前言:
語音聽寫:
把語音(≤60秒)轉換成對應的文字信息,讓機器能夠“聽懂”人類語言,相當於給機器安裝上“耳朵”,使其具備“能聽”的功能
語音轉寫【音頻流識別】:
語音轉寫(Long Form ASR)基於深度全序列卷積神經網絡,將長段音頻(5小時以內)數據轉換成文本數據,爲信息處理和數據挖掘提供基礎
效果圖:
1、科大訊飛官網,點擊此處>>>
地址:https://www.xfyun.cn/
①、註冊完成後最好實名認證下:
②、創建應用(這裏名字很容易重名,建議取長一些):
③、創建好後獲得APPID:
④:下載對應SDK資源:
⑤:解壓文件夾依次點開Downloads\sample\speechDemo\libs:
注意Msc.jar右鍵添加Add As Library,並檢查build.gradle中是否添加成功:
implementation files('libs/Msc.jar')
同時還需添加如下代碼(否則會提示:創建對象失敗,請確認 libmsc.so 放置正確,且有調用 createUtility 進行初始化):
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
注意:如需在打包或者生成APK的時候進行混淆,請在proguard.cfg中添加如下代碼:
-keep class com.iflytek.**{*;}
-keepattributes Signature
2、代碼部分:
①:AndroidManifest.xml
<!--連接網絡權限,用於執行雲端語音能力 -->
<uses-permission android:name="android.permission.INTERNET"/>
<!--獲取手機錄音機使用權限,聽寫、識別、語義理解需要用到此權限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!--讀取網絡信息狀態 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!--獲取當前wifi狀態 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<!--允許程序改變網絡連接狀態 -->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<!--外存儲寫權限,構建語法需要用到此權限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--外存儲讀權限,構建語法需要用到此權限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!--配置權限,用來記錄應用配置信Application息 -->
<uses-permission android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" />
存儲權限用來保存錄音文件,不需要可不添加
②、初始化即創建語音配置對象,只有初始化後纔可以使用MSC的各項服務。建議將初始化放在程序入口處(如Application、Activity的onCreate方法),初始化代碼如下:
public class MyApplication extends Application {
@Override
public void onCreate() {
// 將“12345678”替換成您申請的APPID,申請地址:http://www.xfyun.cn
// 請勿在“=”與appid之間添加任何空字符或者轉義符
SpeechUtility.createUtility(this, SpeechConstant.APPID +"=12345678");
// 以下語句用於設置日誌開關(默認開啓),設置成false時關閉語音雲SDK日誌打印
// Setting.setShowLog(false);
super.onCreate();
}
}
並在AndroidManifest.xml中註冊
③、代碼中的使用MainActivity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
// 語音聽寫對象
private SpeechRecognizer mIat;
// 語音聽寫UI
private RecognizerDialog mIatDialog;
// 用HashMap存儲聽寫結果
private HashMap<String, String> mIatResults = new LinkedHashMap<>();
private EditText mResultText;
private Button languageText, dialogButton;
// 語言類型【默認中文】
private String language = "zh_cn";
// 格式類型【默認json】
private String resultType = "json";
private boolean cyclic = false;//音頻流識別是否循環調用
//拼接字符串
private StringBuffer buffer = new StringBuffer();
//Handler碼
private int handlerCode = 0x123;
// 函數調用返回值
private int resultCode = 0;
// 切換中英文
private boolean languageType;
// 彈框是否顯示
private int dialogType;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化控件
findViewById(R.id.iat_recognize).setOnClickListener(this);
findViewById(R.id.iat_recognize_stream).setOnClickListener(this);
findViewById(R.id.iat_stop).setOnClickListener(this);
findViewById(R.id.iat_cancel).setOnClickListener(this);
mResultText = this.findViewById(R.id.iat_text);
languageText = this.findViewById(R.id.languageText);
dialogButton = this.findViewById(R.id.dialogButton);
languageText.setOnClickListener(this);
dialogButton.setOnClickListener(this);
// 初始化識別無UI識別對象
// 使用SpeechRecognizer對象,可根據回調消息自定義界面;
mIat = SpeechRecognizer.createRecognizer(this, mInitListener);
// 初始化聽寫Dialog,如果只使用有UI聽寫功能,無需創建SpeechRecognizer
// 使用UI聽寫功能,請根據sdk文件目錄下的notice.txt,放置佈局文件和圖片資源
mIatDialog = new RecognizerDialog(MainActivity.this, mInitListener);
}
@Override
public void onClick(View view) {
if (null == mIat) {
// 創建單例失敗,與 21001 錯誤爲同樣原因,
// 參考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
showToast("創建對象失敗,請確認 libmsc.so 放置正確,且有調用 createUtility 進行初始化");
return;
}
switch (view.getId()) {
// 開始聽寫
// 如何判斷一次聽寫結束:OnResult isLast=true 或者 onError
case R.id.iat_recognize:
buffer.setLength(0);//長度清空
mResultText.setText(null);// 清空顯示內容
mIatResults.clear();//清除存貯結果
// 設置參數
setParam();
if (dialogType == 0) {
// 顯示聽寫對話框
mIatDialog.setListener(mRecognizerDialogListener);
mIatDialog.show();
showToast("開始聽寫");
} else if (dialogType == 1) {
// 不顯示聽寫對話框
resultCode = mIat.startListening(mRecognizerListener);
if (resultCode != ErrorCode.SUCCESS) {
showToast("聽寫失敗,錯誤碼:" + ",請點擊網址https://www.xfyun.cn/document/error-code查詢解決方案");
} else {
showToast("開始聽寫");
}
} else if (dialogType == 2) {
// 自定義聽寫對話框
showAlertDialog();
resultCode = mIat.startListening(mRecognizerListener);
if (resultCode != ErrorCode.SUCCESS) {
showToast("聽寫失敗,錯誤碼:" + ",請點擊網址https://www.xfyun.cn/document/error-code查詢解決方案");
} else {
showToast("開始聽寫");
}
}
break;
// 音頻流識別
case R.id.iat_recognize_stream:
executeStream();
break;
case R.id.languageText:
if (languageType) {
languageType = false;
language = "zh_cn";
languageText.setText("點擊切換語種:中文");
} else {
languageType = true;
language = "en_us";
languageText.setText("點擊切換語種:英文");
}
mIat.setParameter(SpeechConstant.LANGUAGE, language);
break;
// 停止聽寫
case R.id.iat_stop:
mIat.stopListening();
showToast("停止聽寫");
break;
// 取消聽寫
case R.id.iat_cancel:
mIat.cancel();
showToast("取消聽寫");
break;
//默認顯示彈框
case R.id.dialogButton:
if (dialogType == 0) {
dialogType = 1;
dialogButton.setText("不顯示訊飛彈框");
} else if (dialogType == 1) {
dialogType = 2;
dialogButton.setText("顯示自定義彈框");
} else if (dialogType == 2) {
dialogButton.setText("顯示訊飛彈框");
dialogType = 0;
}
break;
}
}
/**
* 初始化監聽器。
*/
private InitListener mInitListener = new InitListener() {
@Override
public void onInit(int code) {
Log.e(TAG, "SpeechRecognizer init() code = " + code);
if (code != ErrorCode.SUCCESS) {
showToast("初始化失敗,錯誤碼:" + code + ",請點擊網址https://www.xfyun.cn/document/error-code查詢解決方案");
}
}
};
/**
* 聽寫監聽器。
*/
private RecognizerListener mRecognizerListener = new RecognizerListener() {
@Override
public void onBeginOfSpeech() {
// 此回調錶示:sdk內部錄音機已經準備好了,用戶可以開始語音輸入
showToast("開始說話");
}
@Override
public void onError(SpeechError error) {
// Tips:
// 錯誤碼:10118(您沒有說話),可能是錄音機權限被禁,需要提示用戶打開應用的錄音權限。
showToast(error.getPlainDescription(true));
if (null != dialog) {
dialog.dismiss();
}
}
@Override
public void onEndOfSpeech() {
// 此回調錶示:檢測到了語音的尾端點,已經進入識別過程,不再接受語音輸入
showToast("結束說話");
if (null != dialog) {
dialog.dismiss();
}
}
@Override
public void onResult(RecognizerResult results, boolean isLast) {
Log.e(TAG, "onResult: " + results.getResultString());
if (resultType.equals(resultType)) {
printResult(results);
} else if (resultType.equals("plain")) {
buffer.append(results.getResultString());
mResultText.setText(buffer.toString());
mResultText.setSelection(mResultText.length());
}
if (isLast & cyclic) {
// TODO 最後的結果
Message message = Message.obtain();
message.what = handlerCode;
handler.sendMessageDelayed(message, 100);
}
}
@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提供給技術支持人員,可用於查詢會話日誌,定位出錯原因
// 若使用本地能力,會話id爲null
// if (SpeechEvent.EVENT_SESSION_ID == eventType) {
// String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
// Log.d(TAG, "session id =" + sid);
// }
}
};
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == handlerCode) {
executeStream();
}
}
};
/**
* 聽寫UI監聽器
*/
private RecognizerDialogListener mRecognizerDialogListener = new RecognizerDialogListener() {
/**
* 識別回調成功
*/
public void onResult(RecognizerResult results, boolean isLast) {
printResult(results);
}
/**
* 識別回調錯誤.
*/
public void onError(SpeechError error) {
showToast(error.getPlainDescription(true));
}
};
/**
* 打印聽寫結果
*/
private void printResult(RecognizerResult results) {
String text = JsonParser.parseIatResult(results.getResultString());
String sn = null;
// 讀取json結果中的sn字段
try {
JSONObject resultJson = new JSONObject(results.getResultString());
sn = resultJson.optString("sn");
} catch (JSONException e) {
e.printStackTrace();
}
mIatResults.put(sn, text);
StringBuffer resultBuffer = new StringBuffer();
for (String key : mIatResults.keySet()) {
resultBuffer.append(mIatResults.get(key));
}
mResultText.setText(resultBuffer.toString());
mResultText.setSelection(mResultText.length());
}
/**
* 聽寫參數設置
*/
public void setParam() {
// 清空參數
mIat.setParameter(SpeechConstant.PARAMS, null);
// 設置聽寫引擎類型
mIat.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
// 設置返回結果格式【目前支持json,xml以及plain 三種格式,其中plain爲純聽寫文本內容】
mIat.setParameter(SpeechConstant.RESULT_TYPE, resultType);
//目前Android SDK支持zh_cn:中文、en_us:英文、ja_jp:日語、ko_kr:韓語、ru-ru:俄語、fr_fr:法語、es_es:西班牙語、
// 注:小語種若未授權無法使用會報錯11200,可到控制檯-語音聽寫(流式版)-方言/語種處添加試用或購買。
mIat.setParameter(SpeechConstant.LANGUAGE, language);
// 設置語言區域、當前僅在LANGUAGE爲簡體中文時,支持方言選擇,其他語言區域時,可把此參數值設爲mandarin。
// 默認值:mandarin,其他方言參數可在控制檯方言一欄查看。
mIat.setParameter(SpeechConstant.ACCENT, "mandarin");
//獲取當前語言(同理set對應get方法)
Log.e(TAG, "last language:" + mIat.getParameter(SpeechConstant.LANGUAGE));
//此處用於設置dialog中不顯示錯誤碼信息
//mIat.setParameter("view_tips_plain","false");
//開始錄入音頻後,音頻後面部分最長靜音時長,取值範圍[0,10000ms],默認值5000ms
mIat.setParameter(SpeechConstant.VAD_BOS, "5000");
// 設置語音後端點:後端點靜音檢測時間,即用戶停止說話多長時間內即認爲不再輸入, 自動停止錄音取值範圍[0,10000ms],默認值1800ms。
mIat.setParameter(SpeechConstant.VAD_EOS, "1800");
// 設置標點符號,設置爲"0"返回結果無標點,設置爲"1"返回結果有標點
mIat.setParameter(SpeechConstant.ASR_PTT, "1");
// 設置音頻保存路徑,保存音頻格式支持pcm、wav,設置路徑爲sd卡請注意WRITE_EXTERNAL_STORAGE權限
mIat.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
mIat.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory() + "/msc/helloword.wav");
}
//執行音頻流識別操作
private void executeStream() {
buffer.setLength(0);
mResultText.setText(null);// 清空顯示內容
mIatResults.clear();
//設置參數
setParam();
//設置音頻來源爲外部文件
mIat.setParameter(SpeechConstant.AUDIO_SOURCE, "-1");
mIat.setParameter(SpeechConstant.LANGUAGE, language);
//也可以像以下這樣直接設置音頻文件路徑識別(要求設置文件在sdcard上的全路徑):
//mIat.setParameter(SpeechConstant.AUDIO_SOURCE, "-2");
//mIat.setParameter(SpeechConstant.ASR_SOURCE_PATH, "sdcard/XXX/XXX.pcm");
resultCode = mIat.startListening(mRecognizerListener);
if (resultCode != ErrorCode.SUCCESS) {
showToast("識別失敗,錯誤碼:" + resultCode + ",請點擊網址https://www.xfyun.cn/document/error-code查詢解決方案");
} else {
byte[] audioData = FucUtil.readAudioFile(MainActivity.this, "iattest.wav");
if (null != audioData) {
showToast("開始音頻流識別");
// 一次(也可以分多次)寫入音頻文件數據,數據格式必須是採樣率爲8KHz或16KHz(本地識別只支持16K採樣率,雲端都支持),位長16bit,單聲道的wav或者pcm
// 寫入8KHz採樣的音頻時,必須先調用setParameter(SpeechConstant.SAMPLE_RATE, "8000")設置正確的採樣率
// 注:當音頻過長,靜音部分時長超過VAD_EOS將導致靜音後面部分不能識別。
ArrayList<byte[]> bytes = FucUtil.splitBuffer(audioData, audioData.length, audioData.length / 3);
for (int i = 0; i < bytes.size(); i++) {
mIat.writeAudio(bytes.get(i), 0, bytes.get(i).length);
try {
Thread.sleep(1000);//休眠1秒
} catch (Exception e) {
}
}
//mIat.writeAudio(audioData, 0, audioData.length );
mIat.stopListening();
} else {
mIat.cancel();
showToast("讀取音頻流失敗");
}
}
}
@Override
protected void onResume() {
// 開放統計 移動數據統計分析
/*FlowerCollector.onResume(MainActivity.this);
FlowerCollector.onPageStart(TAG);*/
super.onResume();
}
@Override
protected void onPause() {
// 開放統計 移動數據統計分析
super.onPause();
}
/**
* 展示吐司
*/
private void showToast(final String str) {
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
}
private AlertDialog dialog;
private void showAlertDialog() {
dialog = new AlertDialog.Builder(this)
.setTitle("自定彈框")//標題
.setMessage("正在識別,請稍後...")//內容
.setIcon(R.mipmap.ic_launcher)//圖標
.create();
dialog.show();
}
}
對應佈局文件:
<?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:layout_margin="15dp"
android:text="訊飛聽寫示例"
android:textSize="30sp" />
<EditText
android:id="@+id/iat_text"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="top|left"
android:hint="聽寫結果顯示"
android:paddingBottom="10dp"
android:textSize="20sp" />
<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/iat_recognize"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="開始"
android:textSize="20sp" />
<Button
android:id="@+id/iat_stop"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="停止"
android:textSize="20sp" />
<Button
android:id="@+id/iat_cancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="取消"
android:textSize="20sp" />
</LinearLayout>
<Button
android:id="@+id/dialogButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="顯示訊飛彈框"
android:textSize="20sp" />
<Button
android:id="@+id/languageText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="點擊切換語種:中文"
android:textSize="20sp" />
<Button
android:id="@+id/iat_recognize_stream"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="音頻流識別"
android:textSize="20sp" />
</LinearLayout>
注意:
①、如果需要訊飛自帶聽寫UI【RecognizerDialog】:
將此文件夾拷貝下來speechDemo\src\main\assets\iflytek(否則找不到資源會報錯)
②、文中setParam方法中最後2句代碼是否保存音頻:
//自行更換保存路徑
mIat.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory() + "/msc/helloword.wav");
錄製後可到手機文件管理查找,樓主使用的MEIZU 16th直接可以搜索到,並且可以原生播放:
③、音頻流iattest.wav如上圖也需拷貝
byte[] audioData = FucUtil.readAudioFile(MainActivity.this, "iattest.wav");
3、所使用到的工具類(Demo中也有,刪除了未使用到的):
①:FucUtil.java
/**
* 功能性函數擴展類
*/
public class FucUtil {
/**
* 將字節緩衝區按照固定大小進行分割成數組
*
* @param buffer 緩衝區
* @param length 緩衝區大小
* @param spsize 切割塊大小
* @return
*/
public static ArrayList<byte[]> splitBuffer(byte[] buffer, int length, int spsize) {
ArrayList<byte[]> array = new ArrayList<byte[]>();
if (spsize <= 0 || length <= 0 || buffer == null || buffer.length < length)
return array;
int size = 0;
while (size < length) {
int left = length - size;
if (spsize < left) {
byte[] sdata = new byte[spsize];
System.arraycopy(buffer, size, sdata, 0, spsize);
array.add(sdata);
size += spsize;
} else {
byte[] sdata = new byte[left];
System.arraycopy(buffer, size, sdata, 0, left);
array.add(sdata);
size += left;
}
}
return array;
}
/**
* 讀取asset目錄下音頻文件。
*
* @return 二進制文件數據
*/
public static byte[] readAudioFile(Context context, String filename) {
try {
InputStream ins = context.getAssets().open(filename);
byte[] data = new byte[ins.available()];
ins.read(data);
ins.close();
return data;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
②:JsonParser.java
/**
* Json結果解析類
*/
public class JsonParser {
public static String parseIatResult(String json) {
StringBuffer ret = new StringBuffer();
try {
JSONTokener tokener = new JSONTokener(json);
JSONObject joResult = new JSONObject(tokener);
JSONArray words = joResult.getJSONArray("ws");
for (int i = 0; i < words.length(); i++) {
// 轉寫結果詞,默認使用第一個結果
JSONArray items = words.getJSONObject(i).getJSONArray("cw");
JSONObject obj = items.getJSONObject(0);
ret.append(obj.getString("w"));
// 如果需要多候選結果,解析數組其他字段
// for(int j = 0; j < items.length(); j++){
// JSONObject obj = items.getJSONObject(j);
// ret.append(obj.getString("w"));
// }
}
} catch (Exception e) {
e.printStackTrace();
}
return ret.toString();
}
public static String parseGrammarResult(String json) {
StringBuffer ret = new StringBuffer();
try {
JSONTokener tokener = new JSONTokener(json);
JSONObject joResult = new JSONObject(tokener);
JSONArray words = joResult.getJSONArray("ws");
for (int i = 0; i < words.length(); i++) {
JSONArray items = words.getJSONObject(i).getJSONArray("cw");
for (int j = 0; j < items.length(); j++) {
JSONObject obj = items.getJSONObject(j);
if (obj.getString("w").contains("nomatch")) {
ret.append("沒有匹配結果.");
return ret.toString();
}
ret.append("【結果】" + obj.getString("w"));
ret.append("【置信度】" + obj.getInt("sc"));
ret.append("\n");
}
}
} catch (Exception e) {
e.printStackTrace();
ret.append("沒有匹配結果.");
}
return ret.toString();
}
}
特別注意:錄音和存儲權限需動態獲取,這裏不做講解(可先設置>應用>手動打開對應權限)!!!