前言:
語音評測(SpeechEvaluator):
通過智能語音技術自動對發音水平進行評價、發音錯誤、缺陷進行定位和問題分析。目前評音評測提供漢語、英語兩種語言的評測,支持單字(漢語專有)、詞語 和句子朗讀三種題型。
效果圖:
1、IseActivity.java
public class IseActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "IseActivity";
private EditText mEvaTextEditText;
private EditText mResultEditText;
private Button mIseStartButton;
private Button iseLanguages, iseTopic, iseResultLevel;
private EditText iseStartTime, iseEndTime, iseTime;
private String mLastResult;
private SpeechEvaluator mIse;
//切換(漢語、句子、等級)
private String languageType = "zh_cn", topicType = "read_sentence", resultLevelType = "complete";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ise);
mIse = SpeechEvaluator.createEvaluator(this, null);
initUI();
setEvaText();
}
private void initUI() {
mEvaTextEditText = this.findViewById(R.id.ise_eva_text);
mResultEditText = this.findViewById(R.id.ise_result_text);
mIseStartButton = this.findViewById(R.id.ise_start);
iseLanguages = this.findViewById(R.id.ise_languages);
iseTopic = this.findViewById(R.id.ise_topic);
iseResultLevel = this.findViewById(R.id.ise_result_level);
iseStartTime = this.findViewById(R.id.ise_start_time);
iseEndTime = this.findViewById(R.id.ise_end_time);
iseTime = this.findViewById(R.id.ise_time);
mIseStartButton.setOnClickListener(this);
iseLanguages.setOnClickListener(this);
iseTopic.setOnClickListener(this);
iseResultLevel.setOnClickListener(this);
findViewById(R.id.ise_parse).setOnClickListener(this);
findViewById(R.id.ise_stop).setOnClickListener(this);
findViewById(R.id.ise_cancel).setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (null == mIse) {
// 創建單例失敗,與 21001 錯誤爲同樣原因,參考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
showToast("創建對象失敗,請確認 libmsc.so 放置正確,且有調用 createUtility 進行初始化");
return;
}
switch (view.getId()) {
case R.id.ise_start:
if (mIse == null) {
return;
}
String evaText = mEvaTextEditText.getText().toString();
mLastResult = null;
mResultEditText.setText("");
mResultEditText.setHint("請朗讀以上內容");
mIseStartButton.setEnabled(false);
setParams();
int ret = mIse.startEvaluating(evaText, null, mEvaluatorListener);
//以下方法爲通過直接寫入音頻的方式進行評測業務
/*if (ret != ErrorCode.SUCCESS) {
showTip("識別失敗,錯誤碼:" + ret);
} else {
showTip(getString(R.string.text_begin_ise));
byte[] audioData = FucUtil.readAudioFile(IseDemo.this,"isetest.wav");
if(audioData != null) {
//防止寫入音頻過早導致失敗
try{
new Thread().sleep(100);
}catch (InterruptedException e) {
Log.d(TAG,"InterruptedException :"+e);
}
mIse.writeAudio(audioData,0,audioData.length);
mIse.stopEvaluating();
}
}*/
break;
case R.id.ise_parse:
// 解析最終結果
if (!TextUtils.isEmpty(mLastResult)) {
XmlResultParser resultParser = new XmlResultParser();
Result result = resultParser.parse(mLastResult);
if (null != result) {
mResultEditText.setText(result.toString());
} else {
showToast("解析結果爲空");
}
}
break;
case R.id.ise_stop:
if (mIse.isEvaluating()) {
mResultEditText.setHint("評測已停止,等待結果中...");
mIse.stopEvaluating();
}
break;
case R.id.ise_cancel:
mIse.cancel();
mIseStartButton.setEnabled(true);
mResultEditText.setText("");
mResultEditText.setHint("請點擊“開始評測”按鈕");
mLastResult = null;
break;
case R.id.ise_languages:
if (languageType.equals("zh_cn")) {
languageType = "en_us";
iseLanguages.setText("語種:英語");
} else {
languageType = "zh_cn";
iseLanguages.setText("語種:漢語");
}
setEvaText();
break;
case R.id.ise_topic:
if (topicType.equals("read_sentence")) {
topicType = "read_word";
iseTopic.setText("題型:詞語");
} else if (topicType.equals("read_word")) {
topicType = "read_syllable";
iseTopic.setText("題型:單詞");
} else {
topicType = "read_sentence";
iseTopic.setText("題型:句子");
}
setEvaText();
break;
case R.id.ise_result_level:
if (resultLevelType.equals("complete")) {
resultLevelType = "plain";
iseResultLevel.setText("等級:plain");
} else {
resultLevelType = "complete";
iseResultLevel.setText("等級:complete");
}
setEvaText();
break;
default:
}
}
// 設置評測試題
private void setEvaText() {
String text = "";
if ("en_us".equals(languageType)) {
if ("read_word".equals(topicType)) {
text = getString(R.string.text_en_word);
} else if ("read_sentence".equals(topicType)) {
text = getString(R.string.text_en_sentence);
}
} else {
// 中文評測
if ("read_syllable".equals(topicType)) {
text = getString(R.string.text_cn_syllable);
} else if ("read_word".equals(topicType)) {
text = getString(R.string.text_cn_word);
} else if ("read_sentence".equals(topicType)) {
text = getString(R.string.text_cn_sentence);
}
}
mEvaTextEditText.setText(text);
mResultEditText.setText("");
mLastResult = null;
mResultEditText.setHint("請點擊“開始評測”按鈕");
}
private void setParams() {
// 設置評測語言
mIse.setParameter(SpeechConstant.LANGUAGE, languageType);
// 設置需要評測的類型
mIse.setParameter(SpeechConstant.ISE_CATEGORY, topicType);
// 設置結果等級(中文僅支持complete)
mIse.setParameter(SpeechConstant.RESULT_LEVEL, resultLevelType);
mIse.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8");
// 設置語音前端點:靜音超時時間,即用戶多長時間不說話則當做超時處理
mIse.setParameter(SpeechConstant.VAD_BOS, iseStartTime.getText().toString());
// 設置語音後端點:後端點靜音檢測時間,即用戶停止說話多長時間內即認爲不再輸入, 自動停止錄音
mIse.setParameter(SpeechConstant.VAD_EOS, iseEndTime.getText().toString());
// 語音輸入超時時間,即用戶最多可以連續說多長時間;(-1無超時)
mIse.setParameter(SpeechConstant.KEY_SPEECH_TIMEOUT, iseTime.getText().toString());
mIse.setParameter(SpeechConstant.AUDIO_FORMAT_AUE, "opus");
// 設置音頻保存路徑,保存音頻格式支持pcm、wav,設置路徑爲sd卡請注意WRITE_EXTERNAL_STORAGE權限
mIse.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
mIse.setParameter(SpeechConstant.ISE_AUDIO_PATH, Environment.getExternalStorageDirectory().getAbsolutePath() + "/msc/helloword_ise.wav");
//通過writeaudio方式直接寫入音頻時才需要此設置
//mIse.setParameter(SpeechConstant.AUDIO_SOURCE,"-1");
}
// 評測監聽接口
private EvaluatorListener mEvaluatorListener = new EvaluatorListener() {
@Override
public void onResult(EvaluatorResult result, boolean isLast) {
Log.e(TAG, "evaluator result :" + isLast);
if (isLast) {
StringBuilder builder = new StringBuilder();
builder.append(result.getResultString());
if (!TextUtils.isEmpty(builder)) {
mResultEditText.setText(builder.toString());
}
mIseStartButton.setEnabled(true);
mLastResult = builder.toString();
showToast("評測結束");
}
}
@Override
public void onError(SpeechError error) {
mIseStartButton.setEnabled(true);
if (error != null) {
showToast("error:" + error.getErrorCode() + "," + error.getErrorDescription());
mResultEditText.setText("");
mResultEditText.setHint("請點擊“開始評測”按鈕");
} else {
Log.e(TAG, "evaluator over");
}
}
@Override
public void onBeginOfSpeech() {
// 此回調錶示:sdk內部錄音機已經準備好了,用戶可以開始語音輸入
Log.e(TAG, "onBeginOfSpeech: evaluator begin");
}
@Override
public void onEndOfSpeech() {
// 此回調錶示:檢測到了語音的尾端點,已經進入識別過程,不再接受語音輸入
Log.e(TAG, "onEndOfSpeech: evaluator stoped");
}
@Override
public void onVolumeChanged(int volume, byte[] data) {
showToast("當前音量:" + volume);
Log.e(TAG, "onVolumeChanged: 返回音頻數據" + data.length);
}
@Override
public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
// 以下代碼用於獲取與雲端的會話id,當業務出錯時將會話id提供給技術支持人員,可用於查詢會話日誌,定位出錯原因
// if (SpeechEvent.EVENT_SESSION_ID == eventType) {
// String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
// Log.d(TAG, "session id =" + sid);
// }
}
};
/**
* 展示吐司
*/
private void showToast(final String str) {
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
}
@Override
protected void onResume() {
// 開放統計 移動數據統計分析
//FlowerCollector.onResume(IseDemo.this);
//FlowerCollector.onPageStart(TAG);
super.onResume();
}
@Override
protected void onPause() {
// 開放統計 移動數據統計分析
//FlowerCollector.onPageEnd(TAG);
//FlowerCollector.onPause(IseDemo.this);
super.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (null != mIse) {
mIse.destroy();
mIse = null;
}
}
}
佈局文件activity_ise.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="訊飛評測示例"
android:textSize="30sp" />
<EditText
android:id="@+id/ise_eva_text"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginBottom="5dp"
android:gravity="top|left"
android:textSize="20sp" />
<EditText
android:id="@+id/ise_result_text"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="top|left"
android:hint="請點擊“開始評測”按鈕"
android:textSize="16sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<Button
android:id="@+id/ise_languages"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="語種:漢語"
android:textSize="16sp" />
<Button
android:id="@+id/ise_topic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="題型:句子"
android:textSize="16sp" />
<Button
android:id="@+id/ise_result_level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="等級:complete"
android:textSize="16sp" />
</LinearLayout>
<EditText
android:id="@+id/ise_start_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="前端超時:(默認:5000ms)"
android:inputType="number"
android:padding="10dp"
android:textSize="20sp" />
<EditText
android:id="@+id/ise_end_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="後端超時:(默認:1800ms)"
android:inputType="number"
android:padding="10dp"
android:textSize="20sp" />
<EditText
android:id="@+id/ise_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="評測超時:(默認:-1無超時,單位同上)"
android:inputType="number"
android:padding="10dp"
android:textSize="20sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="修改數值後需重新開始評測"
android:textSize="15sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="2dp"
android:gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/ise_start"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="開始評測"
android:textSize="20sp" />
<Button
android:id="@+id/ise_stop"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="停止評測"
android:textSize="20sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="2dp"
android:gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/ise_cancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="取消評測"
android:textSize="20sp" />
<Button
android:id="@+id/ise_parse"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="結果解析"
android:textSize="20sp" />
</LinearLayout>
</LinearLayout>
2、工具類XmlResultParser.java(Demo中也有):
public class XmlResultParser {
public Result parse(String xml) {
if (TextUtils.isEmpty(xml)) {
return null;
}
XmlPullParser pullParser = Xml.newPullParser();
try {
InputStream ins = new ByteArrayInputStream(xml.getBytes());
pullParser.setInput(ins, "utf-8");
FinalResult finalResult = null;
int eventType = pullParser.getEventType();
while (XmlPullParser.END_DOCUMENT != eventType) {
switch (eventType) {
case XmlPullParser.START_TAG:
if ("FinalResult".equals(pullParser.getName())) {
// 只有一個總分的結果
finalResult = new FinalResult();
} else if ("ret".equals(pullParser.getName())) {
finalResult.ret = getInt(pullParser, "value");
} else if ("total_score".equals(pullParser.getName())) {
finalResult.total_score = getFloat(pullParser, "value");
} else if ("xml_result".equals(pullParser.getName())) {
// 詳細結果
return parseResult(pullParser);
}
break;
case XmlPullParser.END_TAG:
if ("FinalResult".equals(pullParser.getName())) {
return finalResult;
}
break;
default:
break;
}
eventType = pullParser.next();
}
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private Result parseResult(XmlPullParser pullParser) {
Result result = null;
// <rec_paper>標籤是否已掃描到
boolean rec_paperPassed = false;
Sentence sentence = null;
Word word = null;
Syll syll = null;
Phone phone = null;
int eventType;
try {
eventType = pullParser.getEventType();
while (XmlPullParser.END_DOCUMENT != eventType) {
switch (eventType) {
case XmlPullParser.START_TAG:
if ("rec_paper".equals(pullParser.getName())) {
rec_paperPassed = true;
} else if ("read_syllable".equals(pullParser.getName())) {
if (!rec_paperPassed) {
result = new ReadSyllableResult();
} else {
readTotalResult(result, pullParser);
}
} else if ("read_word".equals(pullParser.getName())) {
if (!rec_paperPassed) {
result = new ReadWordResult();
String lan = getLanguage(pullParser);
result.language = (null == lan) ? "cn" : lan;
} else {
readTotalResult(result, pullParser);
}
} else if ("read_sentence".equals(pullParser.getName()) ||
"read_chapter".equals(pullParser.getName())) {
if (!rec_paperPassed) {
result = new ReadSentenceResult();
String lan = getLanguage(pullParser);
result.language = (null == lan) ? "cn" : lan;
} else {
readTotalResult(result, pullParser);
}
} else if ("sentence".equals(pullParser.getName())) {
if (null == result.sentences) {
result.sentences = new ArrayList<Sentence>();
}
sentence = createSentence(pullParser);
} else if ("word".equals(pullParser.getName())) {
if (null != sentence && null == sentence.words) {
sentence.words = new ArrayList<Word>();
}
word = createWord(pullParser);
} else if ("syll".equals(pullParser.getName())) {
if (null != word && null == word.sylls) {
word.sylls = new ArrayList<Syll>();
}
syll = createSyll(pullParser);
} else if ("phone".equals(pullParser.getName())) {
if (null != syll && null == syll.phones) {
syll.phones = new ArrayList<Phone>();
}
phone = createPhone(pullParser);
}
break;
case XmlPullParser.END_TAG:
if ("phone".equals(pullParser.getName())) {
syll.phones.add(phone);
} else if ("syll".equals(pullParser.getName())) {
word.sylls.add(syll);
} else if ("word".equals(pullParser.getName())) {
sentence.words.add(word);
} else if ("sentence".equals(pullParser.getName())) {
result.sentences.add(sentence);
} else if ("read_syllable".equals(pullParser.getName())
|| "read_word".equals(pullParser.getName())
|| "read_sentence".equals(pullParser.getName())) {
return result;
}
break;
default:
break;
}
eventType = pullParser.next();
}
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
private void readTotalResult(Result result, XmlPullParser pullParser) {
result.beg_pos = getInt(pullParser, "beg_pos");
result.end_pos = getInt(pullParser, "end_pos");
result.content = getContent(pullParser);
result.total_score = getFloat(pullParser, "total_score");
result.time_len = getInt(pullParser, "time_len");
result.except_info = getExceptInfo(pullParser);
result.is_rejected = getIsRejected(pullParser);
}
private Phone createPhone(XmlPullParser pullParser) {
Phone phone;
phone = new Phone();
phone.beg_pos = getInt(pullParser, "beg_pos");
phone.end_pos = getInt(pullParser, "end_pos");
phone.content = getContent(pullParser);
phone.dp_message = getInt(pullParser, "dp_message");
phone.time_len = getInt(pullParser, "time_len");
return phone;
}
private Syll createSyll(XmlPullParser pullParser) {
Syll syll;
syll = new Syll();
syll.beg_pos = getInt(pullParser, "beg_pos");
syll.end_pos = getInt(pullParser, "end_pos");
syll.content = getContent(pullParser);
syll.symbol = getSymbol(pullParser);
syll.dp_message = getInt(pullParser, "dp_message");
syll.time_len = getInt(pullParser, "time_len");
return syll;
}
private Word createWord(XmlPullParser pullParser) {
Word word;
word = new Word();
word.beg_pos = getInt(pullParser, "beg_pos");
word.end_pos = getInt(pullParser, "end_pos");
word.content = getContent(pullParser);
word.symbol = getSymbol(pullParser);
word.time_len = getInt(pullParser, "time_len");
word.dp_message = getInt(pullParser, "dp_message");
word.total_score = getFloat(pullParser, "total_score");
word.global_index = getInt(pullParser, "global_index");
word.index = getInt(pullParser, "index");
return word;
}
private Sentence createSentence(XmlPullParser pullParser) {
Sentence sentence;
sentence = new Sentence();
sentence.beg_pos = getInt(pullParser, "beg_pos");
sentence.end_pos = getInt(pullParser, "end_pos");
sentence.content = getContent(pullParser);
sentence.time_len = getInt(pullParser, "time_len");
sentence.index = getInt(pullParser, "index");
sentence.word_count = getInt(pullParser, "word_count");
return sentence;
}
private String getLanguage(XmlPullParser pullParser) {
return pullParser.getAttributeValue(null, "lan");
}
private String getExceptInfo(XmlPullParser pullParser) {
return pullParser.getAttributeValue(null, "except_info");
}
private boolean getIsRejected(XmlPullParser pullParser) {
String isRejected = pullParser.getAttributeValue(null, "is_rejected");
if (null == isRejected) {
return false;
}
return Boolean.parseBoolean(isRejected);
}
private String getSymbol(XmlPullParser pullParser) {
return pullParser.getAttributeValue(null, "symbol");
}
private float getFloat(XmlPullParser pullParser, String attrName) {
String val = pullParser.getAttributeValue(null, attrName);
if (null == val) {
return 0f;
}
return Float.parseFloat(val);
}
private String getContent(XmlPullParser pullParser) {
return pullParser.getAttributeValue(null, "content");
}
private int getInt(XmlPullParser pullParser, String attrName) {
String val = pullParser.getAttributeValue(null, attrName);
if (null == val) {
return 0;
}
return Integer.parseInt(val);
}
}
將Demo中result文件夾拷貝至項目中,如下圖所示:
3、使用到的strings.xml:
<string name="text_en_word">"[word]\napple\nbanana\norange"</string>
<string name="text_en_sentence">"The quick brown fox jumps over the lazy dog."</string>
<string name="text_cn_syllable">"知,癡,是"</string>
<string name="text_cn_word">"磁鐵,率領,脆弱,動手,古箏"</string>
<string name="text_cn_sentence">"一座座雪峯插入雲霄,峯頂銀光閃閃,大大小小的湖泊,像顆顆寶石鑲嵌在綵帶般的溝谷中。"</string>