Android开发笔记(一百零八)智能语音

智能语音技术

如今越来越多的app用到了语音播报功能,例如地图导航、天气预报、文字阅读、口语训练等等。语音技术主要分两块,一块是语音转文字,即语音识别;另一块是文字转语音,即语音合成。


对中文来说,和语音播报相关的一个技术是汉字转拼音,想想看,拼音本身就是音节拼读的标记,每个音节对应一段音频,那么一句的拼音便能用一连串的音频流合成而来。汉字转拼音的说明参见《 Android开发笔记(八十三)多语言支持》。


语音合成通常也简称为TTS,即TextToSpeech(从文本到语言)。语音合成技术把文字智能地转化为自然语音流,当然为了避免机械合成的呆板和停顿感,语音引擎还得对语音流进行平滑处理,确保输出的语音音律流畅、感觉自然。


TextToSpeech

Android从1.6开始,就内置了语音合成引擎,即“Pico TTS”。该引擎支持英语、法语、德语、意大利语,但不支持中文,幸好Android从4.0开始允许接入第三方的语音引擎,因此只要我们安装了中文引擎,就能在代码中使用中文语音合成服务。例如,在各大应用市场上下载并安装科大讯飞+,然后在手机操作“系统设置”——“语言和输入法”——“文字转语音(TTS)输出”,如下图所示即可设置中文的语音引擎:



Android的语音合成控件类名是TextToSpeech,下面是该类常用的方法说明:
构造函数 : 第二个参数设置TTSListener对象,要重写onInit方法(通常在这里调用setLanguage方法,因为初始化成功后才能设置语言)。第三个参数设置语音引擎,默认是系统自带的pico,要获取系统支持的所有引擎可调用getEngines方法。
setLanguage : 设置语言。英语为Locale.ENGLISH;法语为Locale.FRENCH;德语为Locale.GERMAN;意大利语为Locale.ITALIAN;汉语普通话为Locale.CHINA(需安装中文引擎,如科大讯飞+)。该方法的返回值有三个,0表示正常,-1表示缺失数据,-2表示不支持该语言。
setSpeechRate : 设置语速。1.0正常语速;0.5慢一半的语速;2.0;快一倍的语速。
setPitch : 设置音调。1.0正常音调;低于1.0的为低音;高于1.0的为高音。
speak : 开始对指定文本进行语音朗读。
synthesizeToFile : 把指定文本的朗读语音输出到文件。
stop : 停止朗读。
shutdown : 关闭语音引擎。
isSpeaking : 判断是否在语音朗读。
getLanguage : 获取当前的语言。
getCurrentEngine : 获取当前的语音引擎。
getEngines : 获取系统支持的所有语音引擎。


下面是TextToSpeech处理语音合成的代码示例:
import java.util.List;
import java.util.Locale;

import android.app.Activity;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.EngineInfo;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;

public class TTSActivity extends Activity implements OnClickListener {
	
	private TextToSpeech mSpeech;
	private EditText et_tts_resource;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_tts);

		et_tts_resource = (EditText) findViewById(R.id.et_tts_resource);
		Button btn_tts_start = (Button) findViewById(R.id.btn_tts_start);
		btn_tts_start.setOnClickListener(this);

		initLanguageSpinner();
		mSpeech = new TextToSpeech(TTSActivity.this, new TTSListener());
	}
	
	private void initLanguageSpinner() {
		ArrayAdapter<String> starAdapter = new ArrayAdapter<String>(this,
				R.layout.spinner_item, mLangArray);
		starAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
		Spinner sp = (Spinner) findViewById(R.id.sp_tts_language);
		sp.setPrompt("请选择语言");
		sp.setAdapter(starAdapter);
		sp.setOnItemSelectedListener(new LanguageSelectedListener());
		sp.setSelection(0);
	}

	private String[] mEngineArray;
	private int mEngine;
	
	private void initEngineSpinner() {
		mEngineArray = new String[mEngineList.size()];
		for(int i=0; i<mEngineList.size(); i++) {
			mEngineArray[i] = mEngineList.get(i).label;
		}
		ArrayAdapter<String> starAdapter = new ArrayAdapter<String>(this,
				R.layout.spinner_item, mEngineArray);
		starAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
		Spinner sp = (Spinner) findViewById(R.id.sp_tts_engine);
		sp.setPrompt("请选择引擎");
		sp.setAdapter(starAdapter);
		sp.setOnItemSelectedListener(new EngineSelectedListener());
		sp.setSelection(0);
	}
	
	@Override
    protected void onDestroy() {
		recycleSpeech();
        super.onDestroy();
    }
	
	private void recycleSpeech() {
        if (mSpeech != null) {
            mSpeech.stop();
            mSpeech.shutdown();
            mSpeech = null;
        }
	}
	
	private String[] mLangArray = {"英语", "法语", "德语", "意大利语", "汉语普通话" };
	private Locale[] mLocaleArray = {
			Locale.ENGLISH, Locale.FRENCH, Locale.GERMAN, Locale.ITALIAN, Locale.CHINA };
	private int mLanguage;
	private String mTextEN = "hello world. This is a TTS demo.";
	private String mTextCN = "白日依山尽,黄河入海流。欲穷千里目,更上一层楼。";
	
	private class LanguageSelectedListener implements OnItemSelectedListener {
		public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
			mLanguage = arg2;
			if (mLocaleArray[mLanguage]==Locale.SIMPLIFIED_CHINESE
					|| mLocaleArray[mLanguage]==Locale.TRADITIONAL_CHINESE) {
				et_tts_resource.setText(mTextCN);
			} else {
				et_tts_resource.setText(mTextEN);
			}
        	if (mEngineList != null) {
    			resetLanguage();
        	}
		}

		public void onNothingSelected(AdapterView<?> arg0) {
		}
	}

	private class EngineSelectedListener implements OnItemSelectedListener {
		public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
			mEngine = arg2;
			recycleSpeech();
			mSpeech = new TextToSpeech(TTSActivity.this, new TTSListener(), 
					mEngineList.get(mEngine).name);
		}

		public void onNothingSelected(AdapterView<?> arg0) {
		}
	}
	
	private void resetLanguage() {
        int result = mSpeech.setLanguage(mLocaleArray[mLanguage]);
        //如果打印为-2,说明不支持这种语言;-1说明缺失数据
        Toast.makeText(TTSActivity.this, "您选择的是"+mLangArray[mLanguage]
        		+",result="+result, Toast.LENGTH_SHORT).show();
        if (result == TextToSpeech.LANG_MISSING_DATA
                || result == TextToSpeech.LANG_NOT_SUPPORTED) {
        }
	}

	@Override
	public void onClick(View v) {
		if (v.getId() == R.id.btn_tts_start) {
			String content = et_tts_resource.getText().toString();
			int result = mSpeech.speak(content, TextToSpeech.QUEUE_FLUSH, null); 
            Toast.makeText(TTSActivity.this, "speak result="+result, Toast.LENGTH_SHORT).show();
        }
	}
	
	private List<EngineInfo> mEngineList;
	private class TTSListener implements OnInitListener {

        @Override  
        public void onInit(int status) {
            if (status == TextToSpeech.SUCCESS) {
            	if (mEngineList == null) {
                	mEngineList = mSpeech.getEngines();
                	initEngineSpinner();
            	} else {
                	resetLanguage();
            	}
            }
        }
        
    }
}


科大讯飞语音

前面提到,只要安装了中文引擎,即可在TextToSpeech中使用中文语音;可是我们没法要求用户再额外下载一个app,正确的做法是在自己app中集成语音sdk。目前中文环境常见的语音sdk主要有科大讯飞、百度语音、捷通华声、云知声等等,开发者可自行选择一个。


sdk集成

科大讯飞语音sdk的集成步骤如下:
1、导入sdk包到libs目录,包括libmsc.so、Msc.jar、Sunflower.jar;
2、到讯飞网站注册并创建新应用,获得appid;
3、自定义一个Application类,在onCreate函数中加入下面代码,注意appid值为第二步申请到的id:
		SpeechUtility.createUtility(MainApplication.this, "appid=5763c4cf");
4、在AndroidManifest.xml中加入必要的权限,以及自定义的Application类;
5、根据demo工程编写代码与布局文件;
6、如果使用了RecognizerDialog控件,则要把demo工程assets目录下的文件原样拷过来;
7、在混淆打包的时候需要添加-keep class com.iflytek.**{*;},


语音识别

科大讯飞的语音识别用的是SpeechRecognizer类,主要方法如下:
createRecognizer : 创建语音识别对象。
setParameter : 设置语音识别的参数。常用参数包括:
--SpeechConstant.ENGINE_TYPE : 设置听写引擎。TYPE_LOCAL表示本地,TYPE_CLOUD表示云端,TYPE_MIX表示混合。
--SpeechConstant.RESULT_TYPE : 设置返回结果格式。json表示json格式。
--SpeechConstant.LANGUAGE : 设置语言。zh_cn表示中文,en_us表示英文。
--SpeechConstant.ACCENT : 设置方言。mandarin表示普通话,cantonese表示粤语,henanese表示河南话。
--SpeechConstant.VAD_BOS : 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理。
--SpeechConstant.VAD_EOS : 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入,自动停止录音。
--SpeechConstant.ASR_PTT : 设置标点符号。0表示返回结果无标点,1表示返回结果有标点。
--SpeechConstant.AUDIO_FORMAT : 设置音频的保存格式。
--SpeechConstant.ASR_AUDIO_PATH : 设置音频的保存路径。
--SpeechConstant.AUDIO_SOURCE : 设置音频的来源。-1表示音频流,与writeAudio配合使用;-2表示外部文件,同时设置ASR_SOURCE_PATH指定文件路径。
--SpeechConstant.ASR_SOURCE_PATH : 设置外部音频文件的路径。
startListening : 开始监听语音输入。参数为RecognizerListener对象,该对象需重写的方法包括:
--onBeginOfSpeech : 内部录音机已经准备好了,用户可以开始语音输入。
--onError : 错误码:10118(您没有说话),可能是录音机权限被禁,需要提示用户打开应用的录音权限。
--onEndOfSpeech : 检测到了语音的尾端点,已经进入识别过程,不再接受语音输入。
--onResult : 识别结束,返回结果串。
--onVolumeChanged : 语音输入过程中的音量大小变化。
--onEvent : 事件处理,一般是业务出错等异常。
stopListening : 结束监听语音。
writeAudio : 把指定的音频流作为语音输入。
cancel : 取消监听。
destroy : 回收语音识别对象。


下面是科大讯飞语音识别的运行截图:



下面是科大讯飞语音识别的代码例子:
import java.util.HashMap;
import java.util.LinkedHashMap;

import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.Toast;

import com.example.exmvoice.R;
import com.example.exmvoice.SettingsActivity;
import com.example.exmvoice.xunfei.util.FucUtil;
import com.example.exmvoice.xunfei.util.JsonParser;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.RecognizerListener;
import com.iflytek.cloud.RecognizerResult;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechRecognizer;
import com.iflytek.cloud.ui.RecognizerDialog;
import com.iflytek.cloud.ui.RecognizerDialogListener;

public class XFRecognizeActivity extends Activity implements OnClickListener {
	private final static String TAG = XFRecognizeActivity.class.getSimpleName();
	// 语音听写对象
	private SpeechRecognizer mRecognize;
	// 语音听写UI
	private RecognizerDialog mRecognizeDialog;
	// 用HashMap存储听写结果
	private HashMap<String, String> mRecognizeResults = new LinkedHashMap<String, String>();

	private EditText mResultText;
	private SharedPreferences mSharedPreferences;
	
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_xunfei_recognize);

		mResultText = ((EditText) findViewById(R.id.xf_recognize_text));
		findViewById(R.id.xf_recognize_start).setOnClickListener(this);
		findViewById(R.id.xf_recognize_stop).setOnClickListener(this);
		findViewById(R.id.xf_recognize_cancel).setOnClickListener(this);
		findViewById(R.id.xf_recognize_stream).setOnClickListener(this);
		findViewById(R.id.xf_recognize_setting).setOnClickListener(this);
		mSharedPreferences = getSharedPreferences(SettingsActivity.PREFER_NAME, Activity.MODE_PRIVATE);
		
		// 初始化识别无UI识别对象,使用SpeechRecognizer对象,可根据回调消息自定义界面;
		mRecognize = SpeechRecognizer.createRecognizer(this, mInitListener);
		// 初始化听写Dialog,如果只使用有UI听写功能,无需创建SpeechRecognizer
		// 使用UI听写功能,请将assets下文件拷贝到项目中
		mRecognizeDialog = new RecognizerDialog(this, mInitListener);
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		// 退出时释放连接
		mRecognize.cancel();
		mRecognize.destroy();
	}

	@Override
	public void onClick(View v) {
		int ret = 0; // 函数调用返回值
		int resid = v.getId();
		if (resid == R.id.xf_recognize_setting) {  // 进入参数设置页面
			Intent intent = new Intent(this, SettingsAc
  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值