目录
如果说时下什么技术最热门,当然属于以ChatGPT为代表的大模型技术。今天我们介绍一下使用飞凌嵌入式的OKMX6ULL开发板和讯飞星火大模型来实现一款智能语音音箱。之所以选择讯飞星火大模型,是因为它是目前在国内得到测试账号最容易的一款AIGC产品,方便网友复现本文的内容。其实大模型应用的API都很接近,网友也很容易在其他平台实现类似的产品。
-
系统架构
系统的整体架构如下图所示,在这里我们充分利用了科大讯飞的语音识别和语音合成的能力,结合讯飞星火大模型技术就可以实现一个智能语音音箱。
-
开发环境的准备
有关讯飞星火大模型API权限的申请可以参考我在B站的视频:利用API访问讯飞星火认知大模型平台_哔哩哔哩_bilibili。讯飞星火大模型提供的API参考程序主要是Python语言,我们曾经在飞凌OKMX6ULL开发板上移植成功了Python 3.8,但是在安装第三方库时遇到了较多问题,因为pip平台对于32位的ARM系统的支持不是很完善,很多库需要自行编译才能在系统上运行,失去了Python快捷开发的优势。所以,我们在飞凌OKMX6ULL开发板采用C语言完成相关的开发工作。讯飞星火大模型和其他讯飞的产品一样,虽然提供了Linux SDK,但是主要是针对x86系统的,无法在ARM系统下使用。为此我们决定使用C语言,利用讯飞的Web API开发软件接入其平台。
我们主要使用两个第三方库,一个是libwebsockets,因为Web API是基于Web Socket的;另一个是OpenSSL,主要是利用其中几个加密或编码相关的函数来实现讯飞接入的鉴权操作。飞凌嵌入式提供了OpenSSL移植的文档和编译后的文件,所以这部分就直接使用即可。下面简要介绍一下libwebsockets交叉编译的方法。
首先从https://libwebsockets.org/上下载最新版本的软件,解压后进入源码目录,修改其中的CMakeList.txt文件,添加如下内容:
SET(CMAKE_INSTALL_PREFIX /home/armdev/libwebsockets-4.3/install)
SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_FIND_ROOT_PATH "/opt/fsl-imx-x11/4.1.15-2.0.0/sysroots/cortexa7hf-neon-poky-linux-gnueabi" )
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
SET(CMAKE_C_COMPILER "arm-poky-linux-gnueabi-gcc -march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7 --sysroot=/opt/fsl-imx-x11/4.1.15-2.0.0/sysroots/cortexa7hf-neon-poky-linux-gnueabi")
SET(CMAKE_CXX_COMPILE "arm-poky-linux-gnueabi-g++ -march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7 --sysroot=/opt/fsl-imx-x11/4.1.15-2.0.0/sysroots/cortexa7hf-neon-poky-linux-gnueabi")
在文件中查找option(LWS_WITH_SSL "Include SSL support (defaults to OpenSSL or similar, mbedTLS if LWS_WITH_MBEDTLS is set)" ON),将其中的ON改成OFF。
然后执行
cmake .
make
make install
就可以在install目录下得到include和lib目录了。
-
星火大模型的开发
-
接口鉴权
接口鉴权是通过生成符合规则的鉴权URL实现的。科大讯飞的在线服务API的接口鉴权流程是相同的,不同的只是应用所提供的API参数和接口地址。详细的说明可以参考以下文档:
星火大模型对于用户和星火服务器之间的时间差有要求,如果两者偏差太大,会返回403错误:HMAC signature cannot be verified, a valid date or x-date header is required for HMAC Authentication。此时,可以使用
ntpdate -u time.windows.com
命令进行网络时间同步,同步一般只需要进行一次,因为OK6ULL开发板具有实时时钟(RTC)并采用电池供电,可以确保时间准确。
星火大模型API调用
星火大模型API的详细说明见:星火认知大模型Web文档 | 讯飞开放平台文档中心。星火大模型有两个版本:1.5和2.0,两者接口基本相同,有两点差异:一个是接口地址,另一个是domain的参数,版本1.5为general,版本2.0为generalv2。Domain参数如果用错了,会导致10012错误,错误信息是“no category route found”。
星火大模型的请求是一次提交,多次返回的流式处理模式,所以必须通过WebSocket协议而无法使用HTTP协议完成。星火的服务器会把回答分成多条消息发送给客户端,然后客户端解析后,将其中的content内容拼接组成最终的结果。
Libwebsockets的回调处理代码大体如下:
static int callback_websocket(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len) {
switch (reason) {
case LWS_CALLBACK_CLIENT_ESTABLISHED:
printf("Connected to server\n");
lws_callback_on_writable(wsi);
break;
case LWS_CALLBACK_CLIENT_RECEIVE: {
// printf("Received data: %.*s\n", (int)len, (char *)in);
int status_val;
char *content = extract_gpt_content(in, &status_val);
printf("%s", content);
if(content)
{
strcat(gpt_result, content);
free(content);
}
exit_tts_flag = status_val == 2;
break;
}
case LWS_CALLBACK_CLIENT_WRITEABLE: {
int msg_len = strlen(gpt_msg);
unsigned char *buffer = (unsigned char *)malloc(LWS_SEND_BUFFER_PRE_PADDING + msg_len + LWS_SEND_BUFFER_POST_PADDING);
memcpy(buffer + LWS_SEND_BUFFER_PRE_PADDING, gpt_msg, msg_len);
lws_write(wsi, buffer + LWS_SEND_BUFFER_PRE_PADDING, msg_len, LWS_WRITE_TEXT);
free(buffer);
break;
}
case LWS_CALLBACK_CLOSED:
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
printf("Closed\n");
break;
default:
break;
}
return 0;
}
-
语音识别和语音合成
语音识别采用的是科大讯飞的语音听写流式接口,用于1分钟内的即时语音转文字技术,支持实时返回识别结果,达到一边上传音频一边获得识别文本的效果。语音合成则使用的是语音合成流式接口将文字信息转化为声音信息。两者都使用的是流式接口,处理的方法和星火大模型的处理方法类似。
语音识别和语音合成部分都使用的PCM直接处理,比较简单,流量消耗也不大。当然科大讯飞支持多种压缩的语音的传输。
如果在OKMX6ULL开发板播放声音时没有声音或者录音时没有声音,请用以下命令调节一下音量:
amixer sset Headphone 110,110
amixer cset name='Playback Volume' 255,255
amixer cset name='Capture Volume' 0,31
amixer sset 'Left Output Mixer PCM' on
amixer sset 'Right Output Mixer PCM' on
amixer cset name='Capture Volume' 63,63
amixer cset name='ADC PCM Capture Volume' 220,220
amixer cset name='Left Input Boost Mixer LINPUT2 Volume' 7
amixer cset name='Left Input Boost Mixer LINPUT3 Volume' 7
amixer cset name='Right Input Boost Mixer RINPUT1 Volume' 7
amixer cset name='Right Input Boost Mixer RINPUT2 Volume' 7
-
演示效果
演示的视频已经发到B站上了:https://www.bilibili.com/video/BV11u4y197Vf/,大家可以观看一下效果。在演示视频中,合成后的语音是保存为文件再播放的,有点小延迟,如果改成实时播放就更好了。