Linux語音控制 專案
本人是基於粵嵌的GEC210和ubuntu14.04開發的,原始碼過長,需要的可以去這裡下載:
https://download.csdn.net/download/weixin_42116930/10807763
語音互動平臺框架:
平臺:Linux+armA8+科大訊飛語音識別平臺
功能:
1、通過檔案檢索可以將固定的目錄下的三種類型的圖片和音樂給檢索出來,然後再利用libjpeg庫和libpng庫來對jpeg圖片和png圖片進行解碼,
再通過直接操作framebuffer來將圖片顯示在LCD屏上,還可以使用觸控式螢幕來切換圖片。而播放音樂就要移植madplay庫並使用當中的命令來播放音樂,
也可以使用觸控式螢幕來切換音樂。
2、拍照功能,利用V4L2來實現採集一幀的影象並把它顯示在LCD屏上。
3、語言互動功能,首先在客戶端實現錄音功能,並將錄製的音訊資料通過socket傳輸到服務端中,服務端就先進行語法構建然後再進行語法識別,
最後將識別的結果儲存在xml檔案中,再通過socket將xml檔案傳輸到客戶端中,客戶端再對這個檔案進行解析,並得到識別的id號,
然後再根據id進行相應的操作,如操作上述兩個功能。
遇到問題的解決方法:通過debug來除錯和上網查詢一些資料來分析。
流程圖:
開啟LCD屏
刷屏刷成白色
眨眼
socket初始化
攝像頭初始化
while迴圈裡
觸控
進行語音識別
根據語音識別做出相關操作
退出迴圈
關閉LCD
客戶端:
開機先顯示幾幅BMP圖片,然後建立SOCKET連線,之後呼叫system函式來開始錄音,並將錄音好的音訊儲存為cmd.pcm檔案,
向伺服器傳送我們錄好的音訊檔案cmd.pcm(因為在Linux系統中,一切皆是檔案,所以在傳送函式裡我們是用open開啟cmd.pcm檔案,
然後讀取該檔案資料,並將資料儲存在我們申請的動態記憶體中,然後再將之傳送給服務端),然後等待服務端那邊傳送資料過來,
傳送過來的是xml結果,然後再將這個資料儲存在我們的本地檔案中(result.xml),之後就是分析xml檔案獲得ID號並返回ID號。然後就在根據ID號進行一些其他的工作。
服務端:
先設定登入引數,然後再建立SOCKET連線,然後在while迴圈裡先進行登入,服務端每識別一次都要先登入,識別完後要登出,
所以每次識別的時候都要重複這個過程。(因為識別完一次語音,onResult都會被佔用,需要通過登出再登陸才能釋放佔用資源)。
然後再構建語法網路,獲取語法ID。之後進行語法識別,識別完之後最後登出。
其中遇到的困難:
之前進行語音識別的時候識別錯誤,總是不能夠識別,一開始我認為是我們錄製的音訊的問題,
因為錄製的音訊不能夠播放出來,所以我就往這方面排查,先單獨進行錄製音訊,然後把音訊利用socket連線傳輸到我們的ubuntu上。
一開始錄製的是wav音訊,利用socket傳輸到ubuntu上然後使其儲存在另一個音訊檔案,但是奇怪的是伺服器雖然接收到了同樣大小的資料,
但是卻不能播放儲存的音訊檔案,開啟音訊檔案來看看,發現數據非常少,跟原版的不一樣,於是發現這裡有問題,通過排查,
原因是我在伺服器的程式中,用陣列來接收資料,這是不行的,具體原因我也不清楚,改成用動態記憶體來儲存傳送過來的資料就ok了。
於是這個問題就解決了,音訊能夠傳輸了。最後在對比下這個成功的例子,發現原來是我進行語言識別的客戶端的傳送的程式漏了一行程式碼,
就是在計算音訊檔案大小完的時候,忘記把指標移到一開始的地方,所以導致我們傳送的資料是空,也就不能識別了。
語音互動平臺總結:
1、顯示圖片
2、要讓開發板能夠播放音樂,首先開發板得要有音效卡驅動,這個取決於核心。由於我是用九鼎製作的核心的,該核心已經有音效卡驅動了,
所以不需要我們去弄。所以我們只需在開發板上安裝一個播放器madplay,進行移植,具體步驟如下:
首先,我們移植madplay需要三個庫檔案:
madplay-0.15.2b.tar.gz
libmad-0.15.1b.tar.gz
libid3tag-0.15.1b.tar.g
下載地址為: http://sourceforge.net/project/showfiles.php?group_id=12349
下載完之後,我們開始進行移植
①:在root目錄上建立一個資料夾madplayer,然後將這三個壓縮檔案放在這個資料夾裡,然後再進行解壓。如下所示:
tar -xvf madplay-0.15.2b.tar.gz
tar -xvf libmad-0.15.1b.tar.gz
tar -xvf libid3tag-0.15.1b.tar.g
②:解壓完之後,我們先檢查下自己的ubuntu裡面有沒有zlib庫,如果沒有的話就要安裝下,(新手)建議都安裝下(不管有沒有)。
首先也是從網上找到zlib庫(http://www.zlib.net/),然後也將其放置到資料夾madplayer中,解壓:
tar -xvf zlib-1.2.8.tar.gz
然後進行配置:
export CC=arm-linux-gcc
./configure -shared --prefix=/opt/libdecode
(這個是我們指定安裝的目錄,可以自己隨意指定。因為我們ubuntu中的/opt目錄中是沒有libcode這個資料夾的,所以我們得要先在opt目錄下建立這個資料夾)
之後進行編譯和安裝:
make 和 make install
安裝完之後,我們還得在ubuntu上敲如下三個命令:
export LDFLAGS="-L/opt/libdecode/lib"
export CFLAGS="-I/opt/libdecode/include"
export CPPFLAGS="-I/opt/libdecode/include"
這是用來匯出環境變數的,如果我們不匯出,那麼等下我們在配置其他庫時會出錯,提示找不到這個標頭檔案等等。
③:進入到libid3tag-0.15.1b.tar.g解壓完之後的目錄libid3tag-0.15.1b中,
然後進行配置:
./configure --host=arm-linux --enable-shared --enable-static --prefix=/opt/libdecode
配置完之後會得到一個Makefile,檢查下Makefile,看看交叉編譯設定是否正確(一般預設的都不是交叉編譯,所以我們得在Makefile中做修改):
CC=gcc 改為 CC=arm-linux-gcc
AR=ar 改為 AR=arm-linux-ar
RANLIB=ranlib 改為 RANLIB=arm-linux-ranlib
之後進行編譯和安裝:
make 和 make install
④:進入到libmad-0.15.1b.tar.gz解壓完之後的目錄libmad-0.15.1b中,
然後進行配置:
./configure --host=arm-linux --enable-shared --enable-static --prefix=/opt/libdecode
配置完之後會得到一個Makefile,檢查下Makefile,看看交叉編譯設定是否正確(一般預設的都不是交叉編譯,所以我們得在Makefile中做修改):
CC=gcc 改為 CC=arm-linux-gcc
AR=ar 改為 AR=arm-linux-ar
RANLIB=ranlib 改為 RANLIB=arm-linux-ranlib
之後進行編譯和安裝:
make 和 make install
⑤:進入到madplay-0.15.2b.tar.gz解壓完之後的目錄madplay-0.15.2b中,
然後進行配置:
./configure --host=arm-linux --enable-shared --enable-static --prefix=/opt/libdecode
配置完之後會得到一個Makefile,檢查下Makefile,看看交叉編譯設定是否正確(一般預設的都不是交叉編譯,所以我們得在Makefile中做修改):
CC=gcc 改為 CC=arm-linux-gcc
AR=ar 改為 AR=arm-linux-ar
RANLIB=ranlib 改為 RANLIB=arm-linux-ranlib
之後進行編譯和安裝:
make 和 make install
這時會生成兩個檔案:madplay和abxtest,放在/opt/libdecode/bin中。
⑥:接下來就是部署我們的動態檔案了,由於我們生成的動態檔案都是生成在/opt/libdecode/lib中,
所以我們要把這裡面的.so檔案都部署到我們的根檔案系統中去(我的根檔案系統是/root/rootfs/rootfs),部署到/root/rootfs/rootfs/usr/lib中,
命令如下:cp /opt/libdecode/lib/*so* /root/rootfs/rootfs/usr/lib 。然後將生成在/opt/libdecode/bin中的madplay複製到我們的/root/rootfs/rootfs/usr/bin中。
這樣就部署完成了,接下來我們可以把一個音樂檔案部署到/root/rootfs/rootfs/usr/bin中,然後讓我們的開發板開機進入到系統中,然後進入到/bin目錄中,執行如下命令:
./madplay 123.mp3
就可以聽到音樂啦。
注意:根檔案系統是自己製作的,然後我是通過nfs來掛載根檔案系統的,因為這樣除錯比較方便。
3、錄音播放
要進行錄音播放,我們得需要安裝兩個庫alsa-lib-1.0.22.tar.bz2和alsa-utils-1.0.22.tar.bz2。接下來我們就進行這兩個庫的配置和安裝。
首先我們在~目錄下建立一個資料夾放這兩個壓縮檔案,然後分別進行解壓:tar -jxvf alsaxxxxxxx
①:alsa-lib-1.0.22.tar.bz2
解壓後會得到:alsa-lib-1.0.22
cd alsa-lib-1.0.22
配置:
./configure --prefix=/root/luyin/alsa-1.0.22 \
--host=arm-none-linux-gnueabi \
--disable-python
注意:--prefix是自己指定的目錄,將來我們編譯和安裝的檔案都是放在這個目錄下。
編譯和安裝:
make
make install
至此這個庫安裝完成,進入到/root/luyin/alsa-1.0.22會看到生成了一些資料夾和檔案等。
②:alsa-utils-1.0.22.tar.bz2
解壓後會得到:alsa-utils-1.0.22
cd alsa-utils-1.0.22
配置:
./configure --prefix=/root/luyin/alsa-1.0.22/ \
--host=arm-none-linux-gnueabi \
--with-alsa-prefix=/root/luyin/alsa-1.0.22/lib/ \
--with-alsa-inc-prefix=/root/luyin/alsa-1.0.22/include/ \
--disable-alsamixer \
--disable-xmlto
./configure --prefix=/opt/alsa-1.0.22/ \
--host=arm-none-linux-gnueabi \
--with-alsa-prefix=/opt/alsa-1.0.22/lib/ \
--with-alsa-inc-prefix=/opt/alsa-1.0.22/include/ \
--disable-alsamixer \
--disable-xmlto
編譯和安裝:
make
make install
③:接下來就是部署了
進入到/root/luyin/alsa-1.0.22/lib,將所有的so檔案拷貝到我們自己製作的根檔案系統中的/usr/lib中。我自己製作的根檔案系統的目錄是:/root/rootfs/rootfs。如下所示:
cp /root/luyin/alsa-1.0.22/lib/*so* /root/rootfs/rootfs/usr/lib
進入到/root/luyin/alsa-1.0.22/bin,將其中所有的檔案拷貝到我們自己製作的根檔案系統中的/usr/bin中。命令如下所示:
cp /root/luyin/alsa-1.0.22/bin/* /root/rootfs/rootfs/usr/bin
進入到/root/luyin/alsa-1.0.22/share,將其中的alsa檔案(因為這個檔案執行的目錄跟我們之前配置的要相同,所以我們要先在我們的根檔案系統中建立root/luyin/alsa-1.0.22/share),
所以複製到的目錄是:/root/rootfs/rootfs/root/luyin/alsa-1.0.22/share。命令如下:
cd /root/rootfs/rootfs/
mkdir root/luyin/alsa-1.0.22/share -p
cp /root/luyin/alsa-1.0.22/share/alsa /root/rootfs/rootfs/root/luyin/alsa-1.0.22/share/
至此,這兩個檔案就安裝完成,我們可以在開發板上測試了。
④:配置音效卡
在開發板中依次執行如下命令:
mkdir /dev/snd
cd /dev/snd
mknod dsp c 14 3
mknod audio c 14 4
mknod mixer c 14 0
mknod controlC0 c 116 0
mknod seq c 116 1
mknod pcmC0D0c c 116 24
mknod pcmC0D0p c 116 16
mknod pcmC0D1c c 116 25
mknod pcmC0D1p c 116 17
mknod timer c 116 33
如果開發板上本身就有了,可以不用執行這個,但還是建議執行下,反正也沒什麼損失。
⑤:錄音和播放
錄音:
arecord -d3 -c1 -r16000 -twav -fS16_LE example.wav
說明:
-d:錄音時長(duration)
-c:音軌(channels)
-r:取樣頻率(rate)
-t:封裝格式(type)
-f:量化位數(format)
執行這條命令時,可能到時候播放錄音時會有噪音,這是由於我們設定的音軌為1的原因,所以把它設定為2就可以了。具體命令如下:
arecord -d3 -c2 -r16000 -twav -fS16_LE example.wav
播放:
aplay example.wav
4、Linux下的科大訊飛語音離線識別
①:首先得要上科大訊飛官網( https://www.xfyun.cn/?ch=bdtg),註冊一個賬號
②:選擇 資料庫 ---> SDK下載 ----> 輸入登入的使用者名稱 ----> 首次使用需要新增應用 ---> 新增描述(應用名稱:xxxx),其他描述自定義
---> 回到SDK下載
---> 選擇剛剛的應用,點選新增 AI功能 (離線命令詞識別),並選中
---> 最後確定SDK下載,選擇 Linux 平臺
③:將下載好的 SDK拷貝到Ubuntu系統的工作區
mkdir /root/AI
cp /mnt/hgfs/windows_share/Linux_aitalk_expxxxxxx.zip /root/AI -----> 注意 Linux_aitalk_expxxxxxx.zip 為自己的實際包名
④:編譯SDK
cd /root/AI
unzip Linux_aitalk_expxxxxxx.zip
/**********************從現有語音中識別命令************************/
首先,進行編譯
cd samples
cd asr_sample/
chmod 777 32bit_make.sh
./32bit_make.sh
其次,將so庫移植到我們的/usr/lib庫中
cp /root/AI/libs/x86/libmsc.so /usr/lib/
最後,測試
cd /root/AI/bin
./asr_sample
/*****************************************************************/
/*********************從現現場錄音中識別命令**********************/
首先,進行編譯
cd samples
cd asr_record_sample
chmod 777 32bit_make.sh
./32bit_make.sh
這時會出現錯誤,提示找不到動態庫和標頭檔案等,所以我們要安裝兩個庫alsa-lib-1.0.22.tar.bz2和alsa-utils-1.0.22.tar.bz2。然後將其生成的檔案部署到相應的地方,比如把生成的標頭檔案拷貝到/usr/include中,將.so檔案拷貝到/usr/lib中就行。反正就是把相應的放到ubuntu中相應的地方去,bin檔案就放到/usr/bin中等等。
弄完之後還是會出現錯誤:/usr/bin/ld: cannot find -lasound
這時我們得要執行系統更新命令:
sudo apt-get update
sudo apt-get remove libasound2
sudo apt-get install alsa-base alsa-utils alsa-source
sudo apt-get install libasound2*
sudo apt-get install fglrx
sudo apt-get install --reinstall ubuntu-desktop
然後執行命令:
./32bit_make.sh
其次,將so庫移植到我們的/usr/lib庫中
cp /root/AI/libs/x86/libmsc.so /usr/lib/
最後,測試
cd /root/AI/bin
./asr_record_sample
/************************************************************************************/
這樣就可以了。
利用socket將語音傳送給主機:
首先得將libxml2-sources-2.9.3.tar庫安裝
安裝步驟:
1、配置:
./configure --prefix=/opt/libdecode --host=arm-none-linux-gnueabi --without-python
2、編譯和安裝:
make、make install
完成這兩個步驟之後,去編譯我們的程式可能會出現這個問題:
fatal error: libxml/xmlmemory.h: No such file or directory
解決辦法:
ln -s /opt/libdecode/include/libxml2/libxml /opt/libdecode/include/libxml
可能還會遇到的問題:
/mnt/hgfs/windows_share/yuyin/include/common.h:27: error: expected '=', ',', ';', 'asm' or '__attribute__' before '*' token
/mnt/hgfs/windows_share/yuyin/include/common.h:29: error: expected '=', ',', ';', 'asm' or '__attribute__' before '*' token
/mnt/hgfs/windows_share/yuyin/include/common.h:30: error: expected ')' before 'doc'
原因:那是因為沒有包含相應的標頭檔案,包含相應的標頭檔案就行了。
視訊:
VIDIOC_S_FMT failed (-1)
執行V4L2編寫的camera測試程式出現如下錯誤:
Capability Informations:
driver: uvcvideo
card: USB2.0 PC Camera
bus_info: usb-0000:02:03.0-1
version: 3.5.7
capabilities: 04000001
VIDIOC_S_FMT failed (-1)
在開發板上執行camera程式時總是出現VIDIOC_S_FMT failed (-1)錯誤,檢視原始碼發現是在 ret = ioctl(fd, VIDIOC_S_FMT, &fmt);時出錯,用原始碼在PC機上除錯執行卻沒問題,可以通過此條語句,goolgle也沒查到什麼有用的資訊。後來在PC機上除錯時發現在有一個除錯程式的情況下再對該程式重啟一個程序除錯也出現這個問題,懷疑出現這個問題的原因是video0裝置被別的程式佔用了,所以無法再次設定FMT,
實驗:殺斷所有佔用視訊裝置的程式或重啟,在一個終端上執行正常,再開個終端執行時也出現相同情況,說明確實是因為被佔用的原因。
以這個思路在開發板上排除了USB CAMERA裝置節點被佔用後,依然無法設定FMT,償試了下先獲取裝置支援的視訊格式,以此設定OK了,同一個攝像頭在PC上的格式是YUV但在開發板上獲取卻變成MJPG了,所以以PC機上的格式設定無法在開發板上執行,格式不同的根本原因應該是UVC驅動不同造成。
檢查了下原始碼發現退出時都沒有close相應的裝置檔案,這樣也會導致重新插拔USB攝像頭時裝置結點的次裝置號增加變成video1,video2....videoN等。
服務端程式流程:
1、先執行以下兩條命令:
system("rm msc -rf");
system("cp .msc msc -rf");
2、設定登陸引數
const char *login_config = "appid = 583467f7"; //登入引數
3、建立socket連線
init_sock();//服務端初始化一次socket,等待客戶端的連線就好,socket不要重複初始化,不讓資源被佔用會直接失敗只能識別一次
4、進入while迴圈中,該迴圈的主要作用:等待客戶端的語音資料,如果客戶端沒有語音資料傳過來,伺服器就會陷入不可控的死迴圈(不能迴圈識別了)
5、迴圈裡面:
①:ret = MSPLogin(NULL, NULL, login_config); //第一個引數為使用者名稱,第二個引數為密碼,傳NULL即可,第三個引數是登入引數
注意:服務端每次識別一次語音,都要先登入;識別完之後要登出;需要重複這個過程;因為識別完一次語音,onResult都會被佔用,需要通過登出再登陸才能釋放佔用資源。
②:第一次使用某語法進行識別,需要先構建語法網路,獲取語法ID,之後使用此語法進行識別,無需再次構建
memset(&asr_data, 0, sizeof(UserData));
build_grammar(&asr_data); //構建語法網路
asr_data是UserData定義的一個結構體:
typedef struct _UserData
{
int build_fini; //標識語法構建是否完成
int update_fini;//標識更新詞典是否完成
int errcode; //記錄語法構建或更新詞典回撥錯誤碼
char grammar_id[MAX_GRAMMARID_LEN]; //儲存語法構建返回的語法ID
}UserData;
③:build_grammar函式解析
函式原型
int build_grammar(UserData *udata)
引數
udata : 使用者資料
返回值: ret
先進行各種定義:
FILE *grm_file = NULL;
char *grm_content = NULL;
unsigned int grm_cnt_len = 0;
int ret = 0;
char *grm_build_params = calloc(1, MAX_PARAMS_LEN);
開啟GRM_FILE檔案(構建離線識別語法網路所用的語法檔案)(const char * GRM_FILE = "cmd.bnf";)
grm_file = fopen(GRM_FILE, "rb");
if(NULL == grm_file) {
printf("開啟\"%s\"檔案失敗![%s]\n", GRM_FILE, strerror(errno));
return -1;
}
計算GRM_FILE檔案的大小長度
fseek(grm_file, 0, SEEK_END);
grm_cnt_len = ftell(grm_file);
fseek(grm_file, 0, SEEK_SET);
向記憶體申請GRM_FILE檔案的大小+1長度的記憶體,即(grm_cnt_len + 1)
grm_content = (char *)malloc(grm_cnt_len + 1);
if (NULL == grm_content)
{
printf("記憶體分配失敗!\n");
fclose(grm_file);
grm_file = NULL;
return -1;
}
將GRM_FILE檔案中的內容讀取到剛剛申請的記憶體grm_content中,最後以‘\0'為結束符號
fread((void*)grm_content, 1, grm_cnt_len, grm_file);
grm_content[grm_cnt_len] = '\0';
關閉GRM_FILE檔案
fclose(grm_file);
grm_file = NULL;
將相關資訊儲存到grm_build_params中
snprintf(grm_build_params, MAX_PARAMS_LEN - 1,
"engine_type = local, \
asr_res_path = %s, sample_rate = %d, \
grm_build_path = %s, ",e'w'we
ASR_RES_PATH,
SAMPLE_RATE_16K,
GRM_BUILD_PATH
);
構建語法,生成語法ID
ret = QISRBuildGrammar("bnf", grm_content, grm_cnt_len, grm_build_params, build_grm_cb, udata);
函式原型
QISRBuildGrammar()
int MSPAPI QISRBuildGrammar ( const char * grammarType,
const char * grammarContent,
unsigned int grammarLength,
const char * params,
GrammarCallBack callback,
void * userData
)
引數
grammarType[in] 語法型別,線上識別採用abnf 格式語法,離線識別採用bnf 格式語法。
grammarContent[in] 語法內容。
grammarLength[in] 語法長度。
params[in] 包含sample_rate(音訊取樣率)、asr_res_path(離線識別資源路徑)、grm_build_path(離線語法生成路徑)
callback[in] 構建語法回撥介面。typedef int ( GrammarCallBack)( int errorCode, const char info, void* userData);
userData[in/out] 使用者資料。
返回
函式呼叫成功返回MSP_SUCCESS,否則返回錯誤程式碼,詳見錯誤碼列表。
該語句呼叫了回撥函式build_grm_cb
釋放記憶體
free(grm_content);
free(grm_build_params);
grm_content = NULL;
返回值
返回ret
④:build_grm_cb回撥函式解析
函式原型
int build_grm_cb(int ecode, const char *info, void *udata)
引數
ecode:記錄語法構建或更新詞典回撥錯誤碼
info: 語法ID
udata: 使用者資料
返回值
返回0
定義一個結構體指標,並初始化為我們傳進來的資料
UserData *grm_data = (UserData *)udata;
將grm_data指標賦值
if (NULL != grm_data) {
grm_data->build_fini = 1;
grm_data->errcode = ecode;
}
判斷語法是否構建成功
if (MSP_SUCCESS == ecode && NULL != info) {
printf("構建語法成功! 語法ID:%s\n", info);
if (NULL != grm_data)
snprintf(grm_data->grammar_id, MAX_GRAMMARID_LEN - 1, "%s", info);
//將info的值賦值給grm_data->grammar_id
}
else
printf("構建語法失敗!%d\n", ecode);
⑤:回到主函式裡面繼續分析
判斷構建語法呼叫是否成功
printf("構建離線識別語法網路...\n");
if(build_grammar(&asr_data) != MSP_SUCCESS)
{
printf("構建語法呼叫失敗!\n");
goto exit;
}
判斷語法構建完成標識
while (1 != asr_data.build_fini)
usleep(200 * 1000);
若為1,則證明語法構建完成
判斷語法構建是否成功
if (MSP_SUCCESS != asr_data.errcode)
goto exit;
列印以下語句
printf("離線識別語法網路構建完成,開始識別...\n");
離線語法識別
run_asr(&asr_data)
⑥:run_asr函式解析
函式原型
run_asr(&asr_data)
引數
asr_data :結構體資料
返回值
0
先定義兩個指標
char *asr_params = calloc(1, MAX_PARAMS_LEN);
const char *asr_audiof = NULL;
離線語法識別引數設定
snprintf(asr_params, MAX_PARAMS_LEN - 1,
"engine_type = local, "
"asr_res_path = %s, sample_rate = %d, "
"grm_build_path = %s, local_grammar = %s, "
"result_type = xml, result_encoding = UTF-8, ",
ASR_RES_PATH,
SAMPLE_RATE_16K,
GRM_BUILD_PATH,
udata->grammar_id
);
選擇進行離線語法識別的語音檔案
asr_audiof = get_audio_file();
列印get_audio_file()函式返回的資料
printf("%c \n",*asr_audiof);
進行語言識別
demo_file(asr_audiof, asr_params);
釋放記憶體
free(asr_params);
⑦:get_audio_file()函式解析
函式原型
const char *get_audio_file(void)
引數
無
返回值
return "pcm/cmd.pcm";
申請記憶體,列印除錯資訊,初始化
char *pcm = calloc(1, 96000);
printf("waiting for data...\n");
gec210_ip = calloc(1, 32);
int n, total_bytes = 96000, m=0;
char *tmp = pcm;
進入一個迴圈中,直到total_bytes<0才退出,迴圈內容為:
讀取通過socket由客戶端傳過來的資料
n = read(sockfd_asr, tmp, total_bytes);
判斷是否讀取成功
if(n == -1)
{
perror("read() failed");
exit(0);
}
if(n == 0)
{
printf("[%d]recv finished. get %d bytes\n", __LINE__, m);
break;
}
總體來說這個迴圈體的作用是讀取通過socket由客戶端傳過來的資料
列印除錯資訊
printf("[%d]recv finished. get %d bytes\n", __LINE__, m);
開啟./pcm/cmd.pcm檔案
int pcmfd = open("./pcm/cmd.pcm", O_WRONLY|O_CREAT|O_TRUNC, 0644);
將pcm裡的內容寫入到./pcm/cmd.pcm檔案中
m = write(pcmfd, pcm, 96000);
列印除錯資訊
printf("%d bytes has been wirtten into cmd.pcm\n", m);
關閉檔案描述符和返回一個值
close(pcmfd);
return "pcm/cmd.pcm";
⑧:demo_file函式解析
函式原型
static void demo_file(const char* audio_file, const char* session_begin_params)
引數
audio_file :傳遞進來的檔案
session_begin_params : 傳遞進來的檔案的引數
返回值
無
先進行各種定義
int errcode = 0;
FILE* f_pcm = NULL;
char* p_pcm = NULL;
unsigned long pcm_count = 0;
unsigned long pcm_size = 0;
unsigned long read_size = 0;
struct speech_rec iat;
struct speech_rec_notifier recnotifier =
{
on_result,
on_speech_begin,
on_speech_end
};
判斷客戶端傳過來的檔案是否為空
if (NULL == audio_file)
goto iat_exit;
開啟檔案
f_pcm = fopen(audio_file, "rb");
if (NULL == f_pcm)
{
printf("\nopen [%s] failed: %s\n",
audio_file, strerror(errno));
goto iat_exit;
}
計算檔案大小
fseek(f_pcm, 0, SEEK_END);
pcm_size = ftell(f_pcm);
fseek(f_pcm, 0, SEEK_SET);
申請記憶體
p_pcm = (char *)malloc(pcm_size);
if (NULL == p_pcm)
{
printf("\nout of memory! \n");
goto iat_exit;
}
將檔案內容讀取到p_pcm中(即剛剛申請的記憶體)
read_size = fread((void *)p_pcm, 1, pcm_size, f_pcm);
if (read_size != pcm_size)
{
printf("\nread [%s] error!\n", audio_file);
goto iat_exit;
}
內容初始化到iat中
errcode = sr_init(&iat, session_begin_params, SR_USER, &recnotifier);
if(errcode != 0)
{
printf("speech recognizer init failed : %d\n", errcode);
goto iat_exit;
}
語音識別函式
errcode = sr_start_listening(&iat);
if(errcode != 0)
{
printf("\nsr_start_listening failed! error code:%d\n", errcode);
goto iat_exit;
}
進入while迴圈裡
進行一些判斷
if (pcm_size < 2 * len)
len = pcm_size;
if (len <= 0)
break;
寫資料
ret = sr_write_audio_data(&iat, &p_pcm[pcm_count], len);
if(0 != ret)
{
printf("\nwrite audio data failed! error code:%d\n", ret);
goto iat_exit;
}
停止語言識別
errcode = sr_stop_listening(&iat);
if(errcode)
{
printf("\nsr_stop_listening failed! error code:%d \n", errcode);
goto iat_exit;
}
回到主函式中
判斷語法識別是否出錯
if((ret=run_asr(&asr_data)) != MSP_SUCCESS)
{
printf("離線語法識別出錯: %d \n", ret);
goto exit;
}
進行後續工作
printf("語音識別完畢.\n");
system("rm msc -rf");//清楚傳送過來的雲隱資料
system("cp .msc msc -rf");
MSPLogout();
sleep(1);
socket傳輸音訊總結:
客戶端傳送音訊步驟:
在執行下列步驟之前,你已經把socket連線建立好了
1、用open把音訊檔案開啟
fd = open("ddhgdw.pcm", O_RDONLY);
2、獲取音訊檔案資料大小
off_t pcm_size = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
3、申請一塊動態記憶體,將音訊檔案資料讀取到這塊記憶體上
char *pcm = calloc(1, pcm_size);
read(fd, pcm, pcm_size);
4、傳送音訊資料
int m = write(sockfd, pcm, pcm_size);
printf("%d bytes has been write into socket!\n", m);
5、釋放記憶體
free(pcm);
服務端接收音訊步驟:
1、先建立一個音訊檔案用來最後儲存客戶端傳送過來的資料
int fd = -1;
fd = open("cmd.pcm",O_RDWR | O_CREAT | O_APPEND);
2、申請一塊動態記憶體,用來接收客戶端傳送過來的資料
char *pcm = calloc(1, 192000);
3、建立一個迴圈,在迴圈裡不斷的接收客戶端發過來的資料,每次接收9600位元組大小,直到客戶端傳送完,然後再把資料寫入到我們剛剛建立的音訊檔案中。
while(1)
{
ret = recv(clifd, pcm, 96000, 0);
printf("成功接收了%d個位元組\n", ret);
write(fd,pcm,ret);
if(ret == 0)
{
break;
}
}
4、釋放記憶體
free(pcm);
注意:不能用陣列來儲存資料。