科大訊飛實時語音喚醒+離線命令詞識別在Linux及ROS下的應用


0.寫在最前面

希望大家收藏:

本文持續更新地址:https://haoqchen.site/2018/04/26/iflytek-awaken-asr/

  • github地址:https://github.com/HaoQChen/iflytek_awaken_asr(喜歡的話幫忙github點個贊唄~~~包含命令行和ROS兩個分支,自行選擇)
  • 因為科大訊飛給的Demo中只有“錄一段時間的音頻然后命令詞識別”、“對一段錄音中是否有喚醒詞進行判斷”,不能夠實現24小時不間斷的進行命令詞識別或者喚醒的Demo。所以我設計了程序實現24小時不間斷錄音,實時檢測有無喚醒詞出現,當有喚醒詞出現時,切換到“一段時間的命令詞識別”功能。(科大訊飛的文檔中說,喚醒功能的QIVWSessionBegin可以修改一個參數實現喚醒+命令詞識別,但是我試了很多次,不懂這里的bnf應該是什么格式的)
  • 本文假設大家對於科大訊飛的語音識別開發平台有一定的了解,文中將不再介紹下載安裝等事項,經常出現的一些不匹配或者構建語法失敗也都是沒認真申請或者替換相關語法資源造成的。

  • 只在Linux平台下進行過試驗,系統版本Ubuntu14.04(64bit)、ROS版本indigo,暫時沒有發現bug。
  • 包括錄音、語音識別等代碼主要參考科大訊飛的SDK,作者加上了命令解析的代碼,並設計整個程序框架,對於作者部分的代碼完全開源,不保留權利。至於科大訊飛部分代碼,請聯系科大訊飛公司。

 

1.整體框架

int main(int argc, char **argv)//這只是主體程序
{
//init iflytek
  int ret = 0 ;
  ret = MSPLogin(NULL, NULL, lgi_param);
  
  memset(&asr_data, 0, sizeof(UserData));
  ret = build_grammar(&asr_data);  //第一次使用某語法進行識別,需要先構建語法網絡,獲取語法ID,之后使用此語法進行識別,無需再次構建
  
  while (1)
  {
    run_ivw(NULL, ssb_param); 
    
    if(g_is_awaken_succeed){
      run_asr(&asr_data);
      g_is_awaken_succeed = FALSE;
    }
    
    if(g_is_order_publiced == FALSE){
      if(g_order==ORDER_BACK_TO_CHARGE){
        play_wav((char*)concat(PACKAGE_PATH, "audios/back_to_charge.wav"));        
      }
      if(g_order == ORDER_FACE_DETECTION){
        play_wav((char*)concat(PACKAGE_PATH, "audios/operating_face_rec.wav"));
      }
      g_is_order_publiced = TRUE;
   }
    
 }
exit:
  MSPLogout();
}

上面是main函數的主體部分

登錄->構建語法->進入while(1)循環

2.語音喚醒

在while(1)循環中,運行run_ivw(NULL, ssb_param); 開啟錄音,創建一個新的線程接收錄音並上傳至服務器等待喚醒結果。此時run_ivw處於阻塞狀態,一直持續到收到喚醒結果,然后停止錄音退出喚醒服務。

void run_ivw(const char *grammar_list ,  const char* session_begin_params)//這只是主題
{
//start QIVW
	session_id=QIVWSessionBegin(grammar_list, session_begin_params, &err_code);
	err_code = QIVWRegisterNotify(session_id, cb_ivw_msg_proc,NULL);
//start record
	err_code = create_recorder(&recorder, iat_cb, (void*)session_id);
	err_code = open_recorder(recorder, get_default_input_dev(), &wavfmt);
	err_code = start_record(recorder);
	
	record_state = MSP_AUDIO_SAMPLE_FIRST;

	while(record_state != MSP_AUDIO_SAMPLE_LAST)
	{
		sleep_ms(200); //阻塞直到喚醒結果出現
		printf("waiting for awaken%d\n", record_state);
	}
	
exit:
	if (recorder) {
		if(!is_record_stopped(recorder))
			stop_record(recorder);
		close_recorder(recorder);
		destroy_recorder(recorder);
		recorder = NULL;
	}
	if (NULL != session_id)
	{
		QIVWSessionEnd(session_id, sse_hints);
	}
}

3.離線命令詞識別

只有當喚醒結果是成功的才運行run_asr(&asr_data),該函數構建離線命令詞識別參數,調用demo_mic函數。該函數的作用與run_ivw函數基本相似,初始化語音識別、開始識別、等待15秒或者識別完成之后關閉錄音

static void demo_mic(const char* session_begin_params)//這只是主體程序
{
	struct speech_rec_notifier recnotifier = {
		on_result,
		on_speech_begin,
		on_speech_end
	};

	errcode = sr_init(&iat, session_begin_params, SR_MIC, &recnotifier);
	errcode = sr_start_listening(&iat);
	/* demo 15 seconds recording */
	while(i++ < 15 && iat.session_id != NULL)
		sleep(1);
	errcode = sr_stop_listening(&iat);
	sr_uninit(&iat);
}

4.識別結果

語音喚醒的回調函數為:

int cb_ivw_msg_proc( const char *sessionID, int msg, int param1, int param2, const void *info, void *userData )//這只是主體部分程序
{
  if (MSP_IVW_MSG_ERROR == msg) //喚醒出錯消息
  {
    g_is_awaken_succeed = FALSE;
    record_state = MSP_AUDIO_SAMPLE_LAST;
  }else if (MSP_IVW_MSG_WAKEUP == msg) //喚醒成功消息
  {
    g_is_awaken_succeed = TRUE;
    record_state = MSP_AUDIO_SAMPLE_LAST;
  }
  int ret = stop_record(recorder);  
  return 0;
}

該函數通過服務器返回的消息判斷喚醒結果,這里我們通過全局變量向主函數以及錄音線程傳遞結果,以及時做出該有的反應。

離線命令詞識別的回調函數:

void on_result(const char *result, char is_last)
{
    if (result) {
	size_t left = g_buffersize - 1 - strlen(g_result);
	size_t size = strlen(result);
	if (left < size) {
	    g_result = (char*)realloc(g_result, g_buffersize + BUFFER_SIZE);
	    if (g_result)
		g_buffersize += BUFFER_SIZE;
	    else {
		printf("mem alloc failed\n");
		return;
	    }
	}
	strncat(g_result, result, size);
	show_result(g_result, is_last);
        g_order = get_order(g_result);
        if(g_order > ORDER_NONE){
          g_is_order_publiced = FALSE;
        }
    }
}

持續獲取結果,然后調用get_order函數獲取結果並返回到全局變量g_order中。這個get_order函數是我根據自己的語法特性編寫的。各位看官可以根據自己的情況做更改。我的語法特性請看下圖:

主要就是todo+order的形式,其中21300-21399是todo的id,21400-21499是order的id。這樣就可以在獲取結果時很明顯地區分開todo和order,進而識別出語義。

 

 

5.ROS下的應用

這個語音喚醒與命令詞識別一開始也主要想用在ROS系統進行機器人語音控制。這里給出了indigo版本的實現。

在這個包中,我定義了一個sr_order的msg,然后定義一個awaken_asr節點,當獲取到識別結果時就發布消息。同時給出了一個listener接收消息的例子。熟悉ROS的朋友應該都知道我在說啥,就不細說了,具體看我github中ROS的分支程序。

參考

科大訊飛開放平台

SDK文檔

寫在最后

科大訊飛工作人員的態度真的很棒,之前調試發現了一個bug,向他們反饋,雖然還沒解決,但是一直在積極跟進。

另外希望科大訊飛可以降低起購量。學生黨真的想用,但是不需要這么多的裝機量。最后在他們官方群哭訴了挺久,他們的工作人員建議我實名制學生身份,然后向他們服務申請了幾個學術用途的裝機量。他們做支持的小姐姐不辭勞苦地忙了很久,最后終於給我申請到了。在此感謝科大訊飛給了我這個機會學習到這些知識。

 

 

 


免責聲明!

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



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