移動(dòng)端Android推流
本文介紹如何使用移動(dòng)端Android SDK來(lái)支持實(shí)時(shí)記錄場(chǎng)景下的音頻識(shí)別流程。
前提條件
SDK關(guān)鍵接口
initialize:初始化SDK。
/** * 初始化SDK,SDK為單例,請(qǐng)先釋放后再次進(jìn)行初始化。請(qǐng)勿在UI線程調(diào)用,可能會(huì)引起阻塞。 * @param callback:事件監(jiān)聽(tīng)回調(diào),參見(jiàn)下文具體回調(diào)。 * @param parameters:json string形式的初始化參數(shù),參見(jiàn)如下說(shuō)明。 * @param level:log打印級(jí)別,值越小打印越多。 * @param save_log:是否保存log為文件,存儲(chǔ)目錄為ticket中的debug_path字段值。注意,log文件無(wú)上限,請(qǐng)注意持續(xù)存儲(chǔ)導(dǎo)致磁盤存滿。 * @return:參見(jiàn)錯(cuò)誤碼:http://bestwisewords.com/document_detail/459864.html。 */ public synchronized int initialize(final INativeNuiCallback callback, String parameters, final Constants.LogLevel level, final boolean save_log)
其中,parameters詳細(xì)說(shuō)明:
參數(shù)
類型
是否必選
說(shuō)明
workspace
String
是
工作目錄路徑,SDK從該路徑讀取配置文件。
app_key
String
是
必須填“default”。
token
String
是
必須填“default”。
url
String
是
創(chuàng)建聽(tīng)悟?qū)崟r(shí)記錄任務(wù)時(shí)返回的會(huì)議MeetingJoinUrl作為音頻流推送地址,在后續(xù)實(shí)時(shí)音頻流識(shí)別時(shí)通過(guò)該地址進(jìn)行推流。
service_mode
String
是
必須填“1”,表示啟用在線功能。
device_id
String
是
設(shè)備標(biāo)識(shí),唯一表示一臺(tái)設(shè)備(如Mac地址/SN/UniquePsuedoID等)。
debug_path
String
否
debug目錄。當(dāng)初始化SDK時(shí)的save_log參數(shù)取值為true時(shí),該目錄用于保存日志文件。
save_wav
String
否
當(dāng)初始化SDK時(shí)的save_log參數(shù)取值為true時(shí),該參數(shù)生效。表示是否保存音頻debug,該數(shù)據(jù)保存在debug目錄中,需要確保debug_path有效可寫。
注意,音頻文件無(wú)上限,請(qǐng)注意持續(xù)存儲(chǔ)導(dǎo)致磁盤存滿。
其中,INativeNuiCallback類型包含如下回調(diào)。
onNuiAudioStateChanged:根據(jù)音頻狀態(tài)進(jìn)行錄音功能的開關(guān)。
/** * 當(dāng)start/stop/cancel等接口調(diào)用時(shí),SDK通過(guò)此回調(diào)通知App進(jìn)行錄音的開關(guān)操作。 * @param state:錄音需要的狀態(tài)(打開/關(guān)閉) */ void onNuiAudioStateChanged(AudioState state);
onNuiNeedAudioData:在回調(diào)中提供音頻數(shù)據(jù)。
/** * 開始識(shí)別時(shí),此回調(diào)被連續(xù)調(diào)用,App需要在回調(diào)中進(jìn)行語(yǔ)音數(shù)據(jù)填充。 * @param buffer:填充語(yǔ)音的存儲(chǔ)區(qū)。 * @param len:需要填充語(yǔ)音的字節(jié)數(shù)。 * @return:實(shí)際填充的字節(jié)數(shù)。 */ int onNuiNeedAudioData(byte[] buffer, int len);
onNuiEventCallback:SDK事件回調(diào)。
/** * SDK主要事件回調(diào) * @param event:回調(diào)事件,參見(jiàn)如下事件列表。 * @param resultCode:參見(jiàn)錯(cuò)誤碼,在出現(xiàn)EVENT_ASR_ERROR事件時(shí)有效。 * @param arg2:保留參數(shù)。 * @param kwsResult:語(yǔ)音喚醒功能(暫不支持)。 * @param asrResult:語(yǔ)音識(shí)別結(jié)果。 */ void onNuiEventCallback(NuiEvent event, final int resultCode, final int arg2, KwsResult kwsResult, AsrResult asrResult);
事件列表:
名稱
說(shuō)明
EVENT_VAD_START
檢測(cè)到人聲起點(diǎn)。
EVENT_VAD_END
檢測(cè)到人聲尾點(diǎn)。
EVENT_ASR_PARTIAL_RESULT
語(yǔ)音識(shí)別中間結(jié)果。
EVENT_ASR_ERROR
根據(jù)錯(cuò)誤碼信息判斷出錯(cuò)原因。
EVENT_MIC_ERROR
錄音錯(cuò)誤,表示SDK連續(xù)2秒未收到任何音頻,可檢查錄音系統(tǒng)是否正常。
EVENT_SENTENCE_START
實(shí)時(shí)語(yǔ)音識(shí)別事件,表示檢測(cè)到一句話開始。
EVENT_SENTENCE_END
實(shí)時(shí)語(yǔ)音識(shí)別事件,表示檢測(cè)到一句話結(jié)束,返回一句完整的結(jié)果。
EVENT_SENTENCE_SEMANTICS
暫不使用。
EVENT_RESULT_TRANSLATED
翻譯結(jié)果。
EVENT_TRANSCRIBER_COMPLETE
停止語(yǔ)音識(shí)別后最終事件
onNuiAudioRMSChanged:音頻能量值回調(diào)。
/** * 運(yùn)行過(guò)程中收到音頻的實(shí)時(shí)音頻能量值 * @param val: 音頻數(shù)據(jù)能量值回調(diào),范圍-160至0,一般用于UI展示語(yǔ)音動(dòng)效 */ public void onNuiAudioRMSChanged(float val);
setParams:以JSON格式設(shè)置SDK參數(shù)。
/** * 以JSON格式設(shè)置參數(shù)。接口需要在initialize之后startDialog之前調(diào)用。 * @param params:參數(shù)信息請(qǐng)參見(jiàn)如下說(shuō)明。 * @return:參見(jiàn)錯(cuò)誤碼:http://bestwisewords.com/document_detail/459864.html。 */ public synchronized int setParams(String params);
params詳細(xì)說(shuō)明:
參數(shù)
類型
是否必選
說(shuō)明
service_type
Integer
是
必須填“4”。此為需要請(qǐng)求的語(yǔ)音服務(wù)類型,聽(tīng)悟?qū)崟r(shí)推流為“4”。
nls_config
JsonObject
是
訪問(wèn)語(yǔ)音服務(wù)相關(guān)的參數(shù)配置,詳見(jiàn)如下。
nls_config.sr_format
String
是
必須填“pcm”。對(duì)應(yīng)的《CreateTask - 創(chuàng)建聽(tīng)悟任務(wù)》中,創(chuàng)建聽(tīng)悟任務(wù)時(shí)也請(qǐng)指定音頻流數(shù)據(jù)的編碼格式為pcm。
nls_config.sample_rate
Integer
是
音頻采樣率,默認(rèn)值:16000Hz。對(duì)應(yīng)的《CreateTask - 創(chuàng)建聽(tīng)悟任務(wù)》中,創(chuàng)建聽(tīng)悟任務(wù)時(shí)也請(qǐng)指定音頻流數(shù)據(jù)的采樣率,當(dāng)前支持 8000 和 16000。
startDialog:開始識(shí)別。
/** * 開始識(shí)別 * @param vad_mode:多種模式,對(duì)于識(shí)別場(chǎng)景,請(qǐng)使用P2T。 * @param dialog_params:json string形式的對(duì)話參數(shù),可不設(shè)置直接傳入空J(rèn)SON字符串。 * @return:參見(jiàn)錯(cuò)誤碼:http://bestwisewords.com/document_detail/459864.html。 */ public synchronized int startDialog(VadMode vad_mode, String dialog_params);
stopDialog:結(jié)束識(shí)別。
/** * 結(jié)束識(shí)別,調(diào)用該接口后,服務(wù)端將返回最終識(shí)別結(jié)果并結(jié)束任務(wù)。 * @return:參見(jiàn)錯(cuò)誤碼:http://bestwisewords.com/document_detail/459864.html。 */ public synchronized int stopDialog();
cancelDialog:立即結(jié)束識(shí)別。
/** * 立即結(jié)束識(shí)別,調(diào)用該接口后,不等待服務(wù)端返回最終識(shí)別結(jié)果就立即結(jié)束任務(wù)。 * @return:參見(jiàn)錯(cuò)誤碼:http://bestwisewords.com/document_detail/459864.html。 */ public synchronized int cancelDialog();
release:釋放SDK。
/** * 釋放SDK資源 * @return:參見(jiàn)錯(cuò)誤碼:http://bestwisewords.com/document_detail/459864.html。 */ public synchronized int release();
GetVersion:獲得當(dāng)前SDK版本信息。
/** * 獲得當(dāng)前SDK版本信息 * @return: 字符串形式的SDK版本信息 */ public synchronized String GetVersion();
調(diào)用步驟
請(qǐng)下載后在聽(tīng)悟的樣例初始化代碼中將Appkey和Token置為default,url置為您創(chuàng)建聽(tīng)悟?qū)崟r(shí)記錄返回的會(huì)議MeetingJoinUrl。
初始化SDK、錄音實(shí)例。
根據(jù)業(yè)務(wù)需求設(shè)置參數(shù)。
調(diào)用startDialog開始識(shí)別。
根據(jù)音頻狀態(tài)回調(diào)onNuiAudioStateChanged,打開錄音機(jī)。
在onNuiNeedAudioData回調(diào)中提供錄音數(shù)據(jù)。
在EVENT_SENTENCE_START事件回調(diào)中表示當(dāng)前開始識(shí)別一個(gè)句子,在EVENT_ASR_PARTIAL_RESULT事件回調(diào)中獲取識(shí)別中間結(jié)果,在EVENT_SENTENCE_END事件回調(diào)中獲得這句話完整的識(shí)別結(jié)果和各相關(guān)信息,在EVENT_RESULT_TRANSLATED事件回調(diào)中獲得翻譯結(jié)果。
調(diào)用stopDialog結(jié)束識(shí)別。并從EVENT_TRANSCRIBER_COMPLETE事件回調(diào)確認(rèn)已停止識(shí)別。
結(jié)束調(diào)用,使用release接口釋放SDK資源。
Proguard配置
如果代碼使用了混淆,請(qǐng)?jiān)趐roguard-rules.pro中配置:
-keep class com.alibaba.idst.nui.*{*;}
代碼示例
您如果有多例需求,也可以直接new對(duì)象進(jìn)行使用。也可采用GetInstance獲得單例。
NUI SDK初始化
//這里獲得資源路徑, 即工作路徑
// 內(nèi)部通過(guò)context.getApplicationContext().getFilesDir().toString() + "/asr_my" 創(chuàng)建工作路徑,
// 例如 /data/user/0/mit.alibaba.nuidemo/files/asr_my
String workspace = CommonUtils.getModelPath(this);
//創(chuàng)建debug路徑
String debug_path = getExternalCacheDir().getAbsolutePath() + "/debug_" + System.currentTimeMillis();
Utils.createDir(debug_path);
//從nuisdk.aar中assets資源拷貝到workspace中
CommonUtils.copyAssetsData(this);
//初始化SDK,注意用戶需要在genInitParams中填入相關(guān)ID信息才可以使用。
NativeNui nui_instance = new NativeNui();
int ret = nui_instance.initialize(this, genInitParams(asset_path,debug_path), Constants.LogLevel.LOG_LEVEL_VERBOSE, true);
其中,genInitParams生成為String JSON字符串,包含資源目錄和用戶信息。其中用戶信息包含如下字段。
private String genInitParams(String workpath, String debugpath) {
String str = "";
try{
// 1. 接口與實(shí)現(xiàn):http://bestwisewords.com/zh/tingwu/interface-and-implementation
// 按文檔步驟,首先創(chuàng)建AccessKey和創(chuàng)建項(xiàng)目
// 然后需要用戶在自己的服務(wù)端調(diào)用CreateTask接口創(chuàng)建實(shí)時(shí)記錄,獲得MeetingJoinUrl
// 此MeetingJoinUrl即為下方url
JSONObject object = new JSONObject();
//賬號(hào)和項(xiàng)目創(chuàng)建
// ak_id ak_secret 如何獲得,請(qǐng)查看http://bestwisewords.com/document_detail/72138.html
object.put("app_key", "default"); // 必填
object.put("token", "default"); // 必填
// url中填入生成的MeetingJoinUrl。
// 由于MeetingJoinUrl生成過(guò)程涉及ak/sk,移動(dòng)端不可存儲(chǔ)賬號(hào)信息,故需要在服務(wù)端生成,并下發(fā)給移動(dòng)端。
// 詳細(xì)請(qǐng)看: http://bestwisewords.com/zh/tingwu/api-tingwu-2023-09-30-createtask
object.put("url", "wss://tingwu-realtime-cn-hangzhou-pre.aliyuncs.com/api/ws/v1?xxxx"); // 必填
//工作目錄路徑,SDK從該路徑讀取配置文件
object.put("workspace", workpath); // 必填, 且需要有讀寫權(quán)限
object.put("device_id", Utils.getDeviceId()); // 必填, 推薦填入具有唯一性的id, 方便定位問(wèn)題
//當(dāng)初始化SDK時(shí)的save_log參數(shù)取值為true時(shí),該參數(shù)生效。表示是否保存音頻debug,該數(shù)據(jù)保存在debug目錄中,需要確保debug_path有效可寫。
// object.put("save_wav", "true");
//debug目錄,當(dāng)初始化SDK時(shí)的save_log參數(shù)取值為true時(shí),該目錄用于保存中間音頻文件。
object.put("debug_path", debugpath);
object.put("service_mode", Constants.ModeFullCloud); // 必填
str = object.toString();
} catch (JSONException e) {
e.printStackTrace();
}
// 注意! str中包含ak_id ak_secret token app_key等敏感信息, 實(shí)際產(chǎn)品中請(qǐng)勿在Log中輸出這類信息!
Log.i(TAG, "InsideUserContext:" + str);
return str;
}
參數(shù)設(shè)置
以JSON字符串形式進(jìn)行設(shè)置。
//設(shè)置相關(guān)識(shí)別參數(shù),具體參考API文檔
// initialize()之后startDialog之前調(diào)用
nui_instance.setParams(genParams());
private String genParams() {
String params = "";
try {
JSONObject nls_config = new JSONObject();
nls_config.put("sample_rate", 16000);
nls_config.put("sr_format", "pcm");
JSONObject tmp = new JSONObject();
tmp.put("nls_config", nls_config);
tmp.put("service_type", Constants.kServiceTypeSpeechTranscriber); // 必填
params = tmp.toString();
} catch (JSONException e) {
e.printStackTrace();
}
return params;
}
開始識(shí)別
通過(guò)startDialog接口開啟監(jiān)聽(tīng)。
nui_instance.startDialog(Constants.VadMode.TYPE_P2T, genDialogParams());
private String genDialogParams() {
String params = "";
try {
JSONObject dialog_param = new JSONObject();
params = dialog_param.toString();
} catch (JSONException e) {
e.printStackTrace();
}
return params;
}
回調(diào)處理
onNuiAudioStateChanged:錄音狀態(tài)回調(diào),SDK內(nèi)部維護(hù)錄音狀態(tài),根據(jù)該狀態(tài)的回調(diào)進(jìn)行錄音機(jī)的開關(guān)操作。
public void onNuiAudioStateChanged(Constants.AudioState state) { Log.i(TAG, "onNuiAudioStateChanged"); if (state == Constants.AudioState.STATE_OPEN) { Log.i(TAG, "audio recorder start"); mAudioRecorder.startRecording(); } else if (state == Constants.AudioState.STATE_CLOSE) { Log.i(TAG, "audio recorder close"); mAudioRecorder.release(); } else if (state == Constants.AudioState.STATE_PAUSE) { Log.i(TAG, "audio recorder pause"); mAudioRecorder.stop(); } }
onNuiNeedAudioData:錄音數(shù)據(jù)回調(diào),在該回調(diào)中填充錄音數(shù)據(jù)。
public int onNuiNeedAudioData(byte[] buffer, int len) { int ret = 0; if (mAudioRecorder.getState() != AudioRecord.STATE_INITIALIZED) { Log.e(TAG, "audio recorder not init"); return -1; } ret = mAudioRecorder.read(buffer, 0, len); //返回值告知SDK讀到了多少數(shù)據(jù)。 //如果返回<0,則表示出錯(cuò)。 //返回0,則表示無(wú)錄音數(shù)據(jù),連續(xù)2s返回0,會(huì)觸發(fā)事件EVENT_MIC_ERROR。 return ret; }
onNuiEventCallback:NUI SDK事件回調(diào),請(qǐng)勿在事件回調(diào)中調(diào)用SDK的接口,可能引起死鎖。
public void onNuiEventCallback(Constants.NuiEvent event, final int resultCode, final int arg2, KwsResult kwsResult, AsrResult asrResult) { Log.i(TAG, "event=" + event + " resultCode=" + resultCode); // asrResult包含task_id,task_id有助于排查問(wèn)題,請(qǐng)用戶進(jìn)行記錄保存。 // // 新版本新增asrResult.allResponse,若為非nullptr和非空,則給出json格式字符串的完整信息。 if (event == Constants.NuiEvent.EVENT_TRANSCRIBER_COMPLETE) { // 實(shí)時(shí)識(shí)別結(jié)束 } else if (event == Constants.NuiEvent.EVENT_ASR_PARTIAL_RESULT) { // 例如展示當(dāng)前句子的識(shí)別中間結(jié)果 showText(asrView, asrResult.asrResult); } else if (event == Constants.NuiEvent.EVENT_SENTENCE_END) { // 例如展示當(dāng)前句子的完整識(shí)別結(jié)果 showText(asrView, asrResult.asrResult); } else if (event == Constants.NuiEvent.EVENT_RESULT_TRANSLATED) { // 例如展示當(dāng)前句子的翻譯結(jié)果 showText(asrView, asrResult.asrResult); } else if (event == Constants.NuiEvent.EVENT_ASR_ERROR) { // asrResult在EVENT_ASR_ERROR中為錯(cuò)誤信息,搭配錯(cuò)誤碼resultCode和其中的task_id更易排查問(wèn)題,請(qǐng)用戶進(jìn)行記錄保存。 } else if (event == Constants.NuiEvent.EVENT_MIC_ERROR) { // EVENT_MIC_ERROR表示2s未傳入音頻數(shù)據(jù),請(qǐng)檢查錄音相關(guān)代碼、權(quán)限或錄音模塊是否被其他應(yīng)用占用。 } else if (event == Constants.NuiEvent.EVENT_DIALOG_EX) { /* unused */ // 此事件可不用關(guān)注 } }
結(jié)束識(shí)別
nui_instance.stopDialog();
釋放SDK
nui_instance.release();