From ec6ceb8d556e647d274efb25fe9295a78c70859c Mon Sep 17 00:00:00 2001 From: joysuff <338421459@qq.com> Date: Mon, 8 Jul 2024 10:59:37 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E5=9B=9B=E7=BB=84=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CMakeLists.txt" | 37 ++ .../chat.c" | 179 +++++++++ .../chat.h" | 9 + .../config.c" | 26 ++ .../config.h" | 9 + .../control.c" | 360 +++++++++++++++++ .../control.h" | 44 +++ .../http.c" | 110 ++++++ .../http.h" | 9 + .../jrsc.c" | 74 ++++ .../jrsc.h" | 18 + .../key.c" | 372 ++++++++++++++++++ .../play.c" | 150 +++++++ .../play.h" | 14 + .../record.c" | 135 +++++++ .../record.h" | 16 + .../stt.c" | 153 +++++++ .../stt.h" | 9 + .../token.c" | 67 ++++ .../token.h" | 7 + .../tts.c" | 227 +++++++++++ .../tts.h" | 21 + .../utils.c" | 36 ++ .../wake.c" | 324 +++++++++++++++ 24 files changed, 2406 insertions(+) create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/CMakeLists.txt" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/chat.c" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/chat.h" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/config.c" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/config.h" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/control.c" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/control.h" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/http.c" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/http.h" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/jrsc.c" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/jrsc.h" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/key.c" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/play.c" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/play.h" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/record.c" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/record.h" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/stt.c" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/stt.h" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/token.c" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/token.h" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/tts.c" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/tts.h" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/utils.c" create mode 100644 "4/\351\241\271\347\233\256\344\273\243\347\240\201/wake.c" diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/CMakeLists.txt" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/CMakeLists.txt" new file mode 100644 index 0000000..3007969 --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/CMakeLists.txt" @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.0.0) +project(voice_assistant VERSION 0.1.0 LANGUAGES C) + +# add_library(record STATIC record.c) + +# add_library(control STATIC control.c) + +add_executable(voice_assistant main.c) + + + + +add_executable(key key.c record.c control.c token.c http.c config.c tts.c play.c jrsc.c chat.c) +target_compile_definitions(key PRIVATE _GNU_SOURCE) +target_link_libraries(key gpiod asound cjson curl uuid resolv) + +add_subdirectory(snowboy) + + +add_executable(wake wake.c record.c stt.c token.c http.c config.c tts.c play.c jrsc.c) +target_link_libraries(wake snowboy-wrapper snowboy-detect cblas m stdc++ asound curl cjson resolv uuid) +target_compile_definitions(wake PRIVATE _GNU_SOURCE) + + +# add_executable(jrsc jrsc.c) +# target_link_libraries(jrsc cjson curl) + +# add_executable(chat chat.c config.c http.c) +# target_link_libraries(chat cjson curl) +# target_compile_definitions(chat PRIVATE _GNU_SOURCE) + +add_executable(utils utils.c) +target_link_libraries(utils uuid resolv) + +# add_executable(tts tts.c config.c http.c play.c) +# target_link_libraries(tts cjson curl uuid resolv asound) +# target_compile_definitions(tts PRIVATE _GNU_SOURCE) \ No newline at end of file diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/chat.c" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/chat.c" new file mode 100644 index 0000000..64b95be --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/chat.c" @@ -0,0 +1,179 @@ +//调用百度云千帆AppBuilder API进行对话 + +#include +#include +#include +#include +#include "config.h" +#include "http.h" + +char* app_id = "62b84ef5-5daf-491d-97a8-5966fb4deb34"; + +//创建会话 +char* create_conversation(char* authtoken) +{ + char* url = "https://qianfan.baidubce.com/v2/app/conversation"; + + //拼接Authorization字段 + char* auth; + asprintf(&auth, "Authorization: Bearer %s", authtoken); + + //添加请求头部字段 + struct curl_slist* headers = NULL; + headers = curl_slist_append(headers, "Content-Type: application/json;charset=utf-8"); + headers = curl_slist_append(headers, auth); + + //准备请求正文 + cJSON* obj = cJSON_CreateObject(); + cJSON_AddStringToObject(obj, "app_id", app_id); + //将JSON对象转换为JSON字符串 + char* json = cJSON_Print(obj); + + //发送请求 + size_t size = strlen(json); + char* response = http_post(url, headers, json, &size); + cJSON_Delete(obj); + free(json); + free(auth); + curl_slist_free_all(headers); + + if (!response) + { + return NULL; + } + + obj = cJSON_ParseWithLength(response, size); + free(response); + + if (!obj) + { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + + cJSON* conversation_id = cJSON_GetObjectItem(obj, "conversation_id"); + if (!conversation_id) + { + fprintf(stderr, "conversation_id 字段不存在\n"); + cJSON_Delete(obj); + return NULL; + } + + char* retval = strdup(conversation_id->valuestring); + + cJSON_Delete(obj); + + return retval; +} + +//调用对话API +//authtoken: 平台密钥 +//conv_id: 会话ID +//query: 用户输入的文本 +//返回值: 对话结果 +char* chat(char* authtoken, char* conv_id, char* query) +{ + char* url = "https://qianfan.baidubce.com/v2/app/conversation/runs"; + + //拼接Authorization字段 + char* auth; + asprintf(&auth, "Authorization: Bearer %s", authtoken); + + //设置请求头部字段 + struct curl_slist* headers = NULL; + headers = curl_slist_append(headers, "Content-Type: application/json;charset=utf-8"); + headers = curl_slist_append(headers, auth); + + //准备请求正文 + cJSON* obj = cJSON_CreateObject(); + cJSON_AddStringToObject(obj, "app_id", app_id); + cJSON_AddStringToObject(obj, "conversation_id", conv_id); + cJSON_AddStringToObject(obj, "query", query); + cJSON_AddBoolToObject(obj, "stream", false); + //将JSON对象转换为JSON字符串 + char* json = cJSON_Print(obj); + + //发送请求 + size_t size = strlen(json); + char* response = http_post(url, headers, json, &size); + cJSON_Delete(obj); + free(json); + curl_slist_free_all(headers); + free(auth); + + if (!response) + { + return NULL; + } + + obj = cJSON_ParseWithLength(response, size); + free(response); + + if (!obj) + { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + + cJSON* answer = cJSON_GetObjectItem(obj, "answer"); + if (!answer) + { + fprintf(stderr, "answer 字段不存在\n"); + puts(cJSON_Print(obj)); + cJSON_Delete(obj); + return NULL; + } + + char* retval = strdup(answer->valuestring); + + cJSON_Delete(obj); + + return retval; +} + +// int main() +// { +// //读取配置文件中的平台密钥 +// cJSON* config = read_config("config.json"); +// if (!config) +// { +// return EXIT_FAILURE; +// } + +// cJSON* authtoken = cJSON_GetObjectItem(config, "authtoken"); +// if (!authtoken) +// { +// fprintf(stderr, "配置文件错误: 找不到 'authtoken' 字段\n"); +// cJSON_Delete(config); +// return EXIT_FAILURE; +// } + +// printf("%s\n", authtoken->valuestring); + +// //创建会话 +// char* conv_id = create_conversation(authtoken->valuestring); + +// printf("conv_id: %s\n", conv_id); + +// //读取用户输入的文本 +// char line[512]; +// while(1) +// { +// printf("> "); +// if (fgets(line, sizeof(line), stdin) == NULL) +// { +// break; +// } + +// //调用百度云千帆AppBuilder API进行对话 +// char* answer = chat(authtoken->valuestring, conv_id, line); +// if (!answer) +// { +// continue; +// } + +// printf("< %s\n", answer); +// } + +// return EXIT_SUCCESS; +// } diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/chat.h" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/chat.h" new file mode 100644 index 0000000..c9fc603 --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/chat.h" @@ -0,0 +1,9 @@ +#ifndef STT_H +#define STT_H + +#include + +char* create_conversation(char* authtoken); +char* chat(char* authtoken, char* conv_id, char* query); + +#endif \ No newline at end of file diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/config.c" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/config.c" new file mode 100644 index 0000000..c7b7973 --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/config.c" @@ -0,0 +1,26 @@ +#include +#include +#include "config.h" + +//读取配置信息 +struct cJSON* read_config(const char* file) +{ + //读取json文件内容 + FILE* fp = fopen(file, "r"); + if (!fp) { + perror(file); + return NULL; + } + //移动文件指针到文件末尾 + fseek(fp, 0, SEEK_END); + //获取文件大小 + long size = ftell(fp); + //移动文件指针到文件开头 + fseek(fp, 0, SEEK_SET); + //将文件内容读取到缓冲区中 + char* buffer = (char*)malloc(size + 1); + fread(buffer, 1, size, fp); + fclose(fp); + //将缓冲区中的内容转换为json对象 + return cJSON_ParseWithLength(buffer, size); +} \ No newline at end of file diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/config.h" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/config.h" new file mode 100644 index 0000000..fd3546b --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/config.h" @@ -0,0 +1,9 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include + +//读取配置信息 +struct cJSON* read_config(const char* file); + +#endif \ No newline at end of file diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/control.c" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/control.c" new file mode 100644 index 0000000..528f7fb --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/control.c" @@ -0,0 +1,360 @@ +#include +#include +#include +#include "control.h" + +int set_capture_switch(const char* card, const char* selem, bool enable) { + int err; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + + // 打开混音器 + if ((err = snd_mixer_open(&handle, 0)) < 0) { + fprintf(stderr, "Mixer %s open error: %s\n", card, snd_strerror(err)); + return err; + } + + // 附加控制接口到混音器 + if ((err = snd_mixer_attach(handle, card)) < 0) { + fprintf(stderr, "Mixer attach %s error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 注册混音器 + if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { + fprintf(stderr, "Mixer register error: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 加载混音器元素 + if ((err = snd_mixer_load(handle)) < 0) { + fprintf(stderr, "Mixer %s load error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 分配简单元素ID + snd_mixer_selem_id_alloca(&sid); + + // 设置简单元素的名称 + snd_mixer_selem_id_set_name(sid, selem); + + // 查找简单元素 + snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid); + if (!elem) { + fprintf(stderr, "Unable to find simple control '%s',%i\n", selem, 0); + snd_mixer_close(handle); + return -ENOENT; + } + + // 设置采集开关(启用或禁用) + if ((err = snd_mixer_selem_set_capture_switch_all(elem, enable ? 1 : 0)) < 0) { + fprintf(stderr, "Unable to set capture switch: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 关闭混音器 + snd_mixer_close(handle); + + return 0; // 成功 +} + +int set_playback_switch(const char* card, const char* selem, bool enable) { + int err; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + + // 打开混音器 + if ((err = snd_mixer_open(&handle, 0)) < 0) { + fprintf(stderr, "Mixer %s open error: %s\n", card, snd_strerror(err)); + return err; + } + + // 附加控制接口到混音器 + if ((err = snd_mixer_attach(handle, card)) < 0) { + fprintf(stderr, "Mixer attach %s error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 注册混音器 + if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { + fprintf(stderr, "Mixer register error: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 加载混音器元素 + if ((err = snd_mixer_load(handle)) < 0) { + fprintf(stderr, "Mixer %s load error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 分配简单元素ID + snd_mixer_selem_id_alloca(&sid); + + // 设置简单元素的名称 + snd_mixer_selem_id_set_name(sid, selem); + + // 查找简单元素 + snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid); + if (!elem) { + fprintf(stderr, "Unable to find simple control '%s',%i\n", selem, 0); + snd_mixer_close(handle); + return -ENOENT; + } + + // 设置回放开关(启用或禁用) + if ((err = snd_mixer_selem_set_playback_switch_all(elem, enable ? 1 : 0)) < 0) { + fprintf(stderr, "Unable to set playback switch: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 关闭混音器 + snd_mixer_close(handle); + + return 0; // 成功 +} + +int get_capture_volume(const char* card, const char* selem) { + int err; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + + // 打开混音器 + if ((err = snd_mixer_open(&handle, 0)) < 0) { + fprintf(stderr, "Mixer %s open error: %s\n", card, snd_strerror(err)); + return err; + } + + // 附加控制接口到混音器 + if ((err = snd_mixer_attach(handle, card)) < 0) { + fprintf(stderr, "Mixer attach %s error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 注册混音器 + if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { + fprintf(stderr, "Mixer register error: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 加载混音器元素 + if ((err = snd_mixer_load(handle)) < 0) { + fprintf(stderr, "Mixer %s load error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 分配简单元素ID + snd_mixer_selem_id_alloca(&sid); + + // 设置简单元素的名称 + snd_mixer_selem_id_set_name(sid, selem); + + // 查找简单元素 + snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid); + if (!elem) { + fprintf(stderr, "Unable to find simple control '%s',%i\n", selem, 0); + snd_mixer_close(handle); + return -ENOENT; + } + + // 获取采集通道音量 + long volume = 0; + if ((err = snd_mixer_selem_get_capture_volume(elem, 0, &volume)) < 0) { + fprintf(stderr, "Unable to get capture volume: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 关闭混音器 + snd_mixer_close(handle); + + return volume; // 成功返回音量 +} + +int get_playback_volume(const char* card, const char* selem) { + int err; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + + // 打开混音器 + if ((err = snd_mixer_open(&handle, 0)) < 0) { + fprintf(stderr, "Mixer %s open error: %s\n", card, snd_strerror(err)); + return err; + } + + // 附加控制接口到混音器 + if ((err = snd_mixer_attach(handle, card)) < 0) { + fprintf(stderr, "Mixer attach %s error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 注册混音器 + if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { + fprintf(stderr, "Mixer register error: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 加载混音器元素 + if ((err = snd_mixer_load(handle)) < 0) { + fprintf(stderr, "Mixer %s load error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 分配简单元素ID + snd_mixer_selem_id_alloca(&sid); + + // 设置简单元素的名称 + snd_mixer_selem_id_set_name(sid, selem); + + // 查找简单元素 + snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid); + if (!elem) { + fprintf(stderr, "Unable to find simple control '%s',%i\n", selem, 0); + snd_mixer_close(handle); + return -ENOENT; + } + + // 获取回放通道音量 + long volume = 0; + if ((err = snd_mixer_selem_get_playback_volume(elem, 0, &volume)) < 0) { + fprintf(stderr, "Unable to get playback volume: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 关闭混音器 + snd_mixer_close(handle); + + return volume; // 成功返回音量 +} + +int set_capture_volume(const char* card, const char* selem, long volume) { + int err; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + + // 打开混音器 + if ((err = snd_mixer_open(&handle, 0)) < 0) { + fprintf(stderr, "Mixer %s open error: %s\n", card, snd_strerror(err)); + return err; + } + + // 附加控制接口到混音器 + if ((err = snd_mixer_attach(handle, card)) < 0) { + fprintf(stderr, "Mixer attach %s error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 注册混音器 + if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { + fprintf(stderr, "Mixer register error: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 加载混音器元素 + if ((err = snd_mixer_load(handle)) < 0) { + fprintf(stderr, "Mixer %s load error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 分配简单元素ID + snd_mixer_selem_id_alloca(&sid); + + // 设置简单元素的名称 + snd_mixer_selem_id_set_name(sid, selem); + + // 查找简单元素 + snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid); + if (!elem) { + fprintf(stderr, "Unable to find simple control '%s',%i\n", selem, 0); + snd_mixer_close(handle); + return -ENOENT; + } + + // 设置采集通道音量 + if ((err = snd_mixer_selem_set_capture_volume_all(elem, volume)) < 0) { + fprintf(stderr, "Unable to set capture volume: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 关闭混音器 + snd_mixer_close(handle); + + return volume; // 成功返回设置的音量 +} + +int set_playback_volume(const char* card, const char* selem, long volume) { + int err; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + + // 打开混音器 + if ((err = snd_mixer_open(&handle, 0)) < 0) { + fprintf(stderr, "Mixer %s open error: %s\n", card, snd_strerror(err)); + return err; + } + + // 附加控制接口到混音器 + if ((err = snd_mixer_attach(handle, card)) < 0) { + fprintf(stderr, "Mixer attach %s error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 注册混音器 + if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { + fprintf(stderr, "Mixer register error: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 加载混音器元素 + if ((err = snd_mixer_load(handle)) < 0) { + fprintf(stderr, "Mixer %s load error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 分配简单元素ID + snd_mixer_selem_id_alloca(&sid); + + // 设置简单元素的名称 + snd_mixer_selem_id_set_name(sid, selem); + + // 查找简单元素 + snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid); + if (!elem) { + fprintf(stderr, "Unable to find simple control '%s',%i\n", selem, 0); + snd_mixer_close(handle); + return -ENOENT; + } + + // 设置回放通道音量 + if ((err = snd_mixer_selem_set_playback_volume_all(elem, volume)) < 0) { + fprintf(stderr, "Unable to set playback volume: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 关闭混音器 + snd_mixer_close(handle); + + return volume; // 成功返回设置的音量 +} diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/control.h" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/control.h" new file mode 100644 index 0000000..768e9d0 --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/control.h" @@ -0,0 +1,44 @@ +#ifndef CONTROL_H +#define CONTROL_H + +#include + +// 设置音频采集开关的函数 +// card: 声卡名称 +// selem: 控制项名称 +// enable: 开关状态 +int set_capture_switch(const char* card, const char* selem, bool enable); + +// 设置音频回放开关的函数 +// card: 声卡名称 +// selem: 控制项名称 +// enable: 开关状态 +int set_playback_switch(const char* card, const char* selem, bool enable); + +// 获取音频采集音量 +// card: 声卡名称 +// selem: 控制项名称 +// 返回值: 当前音量 +int get_capture_volume(const char* card, const char* selem); + +// 获取音频回放通道音量 +// card: 声卡名称 +// selem: 控制项名称 +// 返回值: 当前音量 +int get_playback_volume(const char* card, const char* selem); + +// 设置音频采集音量 +// card: 声卡名称 +// selem: 控制项名称 +// volume: 设置音量 +// 返回值: 成功返回设置后的音量,失败返回错误码 +int set_capture_volume(const char* card, const char* selem, long volume); + +// 设置音频回放通道音量 +// card: 声卡名称 +// selem: 控制项名称 +// volume: 设置音量 +// 返回值: 成功返回设置后的音量,失败返回错误码 +int set_playback_volume(const char* card, const char* selem, long volume); + +#endif // CONTROL_H diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/http.c" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/http.c" new file mode 100644 index 0000000..dbceee8 --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/http.c" @@ -0,0 +1,110 @@ +#include +#include "http.h" + +//发送GET请求 +//url: 请求地址 +//headers: 增加的请求头部字段 +//psize: 响应正文大小,输出参数 +//返回值: 响应正文,需要调用者释放内存 +char* http_get(char* url, struct curl_slist* headers, size_t* psize) +{ + char *respdata; + size_t respsize; + FILE *fp = open_memstream(&respdata, &respsize); + if (!fp) + { + perror("open_memstream"); + return NULL; + } + + CURL *client = curl_easy_init(); + if (!client) + { + perror("curl_easy_init"); + fclose(fp); + return NULL; + } + + curl_easy_setopt(client, CURLOPT_URL, url); + + if (headers) + { + curl_easy_setopt(client, CURLOPT_HTTPHEADER, headers); + } + + // 将服务器返回的响应报文保存到文件流中 + curl_easy_setopt(client, CURLOPT_WRITEDATA, fp); + + CURLcode error = curl_easy_perform(client); + if (error != CURLE_OK) + { + fprintf(stderr, "curl_easy_perform: %s\n", curl_easy_strerror(error)); + curl_easy_cleanup(client); + fclose(fp); + return NULL; + } + + curl_easy_cleanup(client); + fclose(fp); + + //更新响应正文大小 + *psize = respsize; + + return respdata; +} + +//发送POST请求 +//url: 请求地址 +//headers: 增加的请求头部字段 +//data: 请求正文 +//psize: 请求和响应正文大小,输入和输出参数 +//返回值: 响应正文,需要调用者释放内存 +char* http_post(char* url, struct curl_slist* headers, char* data, size_t* psize) +{ + char *respdata; + size_t respsize; + FILE *fp = open_memstream(&respdata, &respsize); + if (!fp) + { + perror("open_memstream"); + return NULL; + } + + CURL *client = curl_easy_init(); + if (!client) + { + perror("curl_easy_init"); + fclose(fp); + return NULL; + } + + curl_easy_setopt(client, CURLOPT_URL, url); + curl_easy_setopt(client, CURLOPT_POST, 1); + curl_easy_setopt(client, CURLOPT_POSTFIELDS, data); + curl_easy_setopt(client, CURLOPT_POSTFIELDSIZE, *psize); + + if (headers) + { + curl_easy_setopt(client, CURLOPT_HTTPHEADER, headers); + } + + // 将服务器返回的响应报文保存到文件流中 + curl_easy_setopt(client, CURLOPT_WRITEDATA, fp); + + CURLcode error = curl_easy_perform(client); + if (error != CURLE_OK) + { + fprintf(stderr, "curl_easy_perform: %s\n", curl_easy_strerror(error)); + curl_easy_cleanup(client); + fclose(fp); + return NULL; + } + + curl_easy_cleanup(client); + fclose(fp); + + //更新响应正文大小 + *psize = respsize; + + return respdata; +} \ No newline at end of file diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/http.h" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/http.h" new file mode 100644 index 0000000..a3c8b73 --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/http.h" @@ -0,0 +1,9 @@ +#ifndef HTTP_H +#define HTTP_H + +#include + +char* http_get(char* url, struct curl_slist* headers, size_t* psize); +char* http_post(char* url, struct curl_slist* headers, char* data, size_t* psize); + +#endif diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/jrsc.c" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/jrsc.c" new file mode 100644 index 0000000..c805497 --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/jrsc.c" @@ -0,0 +1,74 @@ +#include "jrsc.h" + +// 内存写入回调函数 +size_t write_callback(void *ptr, size_t size, size_t nmemb, void *userdata) { + size_t realsize = size * nmemb; + FILE *memstream = (FILE *)userdata; + fwrite(ptr, size, nmemb, memstream); + return realsize; +} + +// 打印推荐的古诗词 +void print_recommended_poetry(const char *response, size_t size) { + cJSON *json = cJSON_ParseWithLength(response, size); + if (json == NULL) { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return; + } + + cJSON *data = cJSON_GetObjectItemCaseSensitive(json, "data"); + if (!cJSON_IsObject(data)) { + fprintf(stderr, "JSON 格式错误: 找不到 'data' 对象\n"); + cJSON_Delete(json); + return; + } + + cJSON *content = cJSON_GetObjectItemCaseSensitive(data, "content"); + if (!cJSON_IsString(content) || (content->valuestring == NULL)) { + fprintf(stderr, "JSON 格式错误: 找不到 'content' 字符串\n"); + cJSON_Delete(json); + return; + } + + printf("今日推荐古诗:%s\n", content->valuestring); + cJSON_Delete(json); +} + +// 获取并打印推荐的古诗词 +void get_and_print_poetry(void) { + CURL *client; + CURLcode err; + char *response; + size_t size; + + FILE *memstream = open_memstream(&response, &size); + if (memstream == NULL) { + perror("open_memstream"); + return; + } + + curl_global_init(CURL_GLOBAL_ALL); + client = curl_easy_init(); + + curl_easy_setopt(client, CURLOPT_URL, "https://v2.jinrishici.com/sentence"); + curl_easy_setopt(client, CURLOPT_WRITEDATA, memstream); + curl_easy_setopt(client, CURLOPT_WRITEFUNCTION, write_callback); + + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "X-User-Token: o9M10gtneoIxRLk5UO13fUG7guYxnS7h"); // 替换 YOUR_API_KEY 为你的 API Key + curl_easy_setopt(client, CURLOPT_HTTPHEADER, headers); + + err = curl_easy_perform(client); + fclose(memstream); + + if (err != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() 失败: %s\n", curl_easy_strerror(err)); + } else { + print_recommended_poetry(response, size); + } + + curl_slist_free_all(headers); + curl_easy_cleanup(client); + free(response); + curl_global_cleanup(); +} diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/jrsc.h" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/jrsc.h" new file mode 100644 index 0000000..199bc0b --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/jrsc.h" @@ -0,0 +1,18 @@ +#ifndef JRSC_H +#define JRSC_H + +#include +#include +#include +#include + +// 内存写入回调函数 +size_t write_callback(void *ptr, size_t size, size_t nmemb, void *userdata); + +// 打印推荐的古诗词 +void print_recommended_poetry(const char *response, size_t size); + +// 获取并打印推荐的古诗词 +void get_and_print_poetry(void); + +#endif // JRSC_H diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/key.c" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/key.c" new file mode 100644 index 0000000..9352de5 --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/key.c" @@ -0,0 +1,372 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "record.h" +#include "control.h" +#include "config.h" +#include "token.h" +#include "http.h" +#include "tts.h" +#include "stt.h" +#include "jrsc.h" +#include "chat.h" + +#define GPIO_LINE_KEY1 9 +#define GPIO_LINE_KEY2 7 +#define GPIO_LINE_KEY3 8 + +char *conv_id = NULL; +// led控制 +void led_control(char *led,char* status) +{ + char file_path[50] = "/sys/class/leds/"; + sprintf(file_path, "%s%s%s", file_path, led, "/brightness"); + FILE *fp = fopen(file_path, "w"); + if (fp == NULL) + { + perror("打开LED设备文件失败"); + return; + } + fprintf(fp, status); + fflush(fp); + // 关闭LED设备文件 + fclose(fp); +} + +// 读取音频文件 +// file: 音频文件路径 +// size: 音频文件大小 +// return: 音频文件内容, NULL 表示失败 +char *load_audio_file(const char *file, size_t *size) +{ + // 打开音频文件 + FILE *fp = fopen(file, "rb"); + if (!fp) + { + perror(file); + return NULL; + } + // 获取文件大小 + fseek(fp, 0, SEEK_END); + *size = ftell(fp); + fseek(fp, 0, SEEK_SET); + // 读取文件内容 + char *buffer = (char *)malloc(*size); + if (!buffer) + { + perror("malloc"); + fclose(fp); + return NULL; + } + fread(buffer, 1, *size, fp); + fclose(fp); + return buffer; +} + +// 发送请求消息 +// token: 获取的access token +// audio: 音频文件内容 +// size: 音频文件大小 +// return: 响应消息正文, NULL 表示失败 +char *send_request(char *token, char *audio, size_t size) +{ + char *url = NULL; + asprintf(&url, "http://vop.baidu.com/server_api?cuid=hqyj&token=%s", token); + + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Content-Type: audio/pcm; rate=16000"); + + char *response = http_post(url, headers, audio, &size); + + free(url); + curl_slist_free_all(headers); + + return response; +} + +// 处理服务器返回的响应消息 +char *process_response(char *response, size_t size) +{ + // 解析 JSON 响应 + cJSON *json = cJSON_ParseWithLength(response, size); + if (!json) + { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + + // 判断err_no字段 + cJSON *err_no = cJSON_GetObjectItem(json, "err_no"); + if (!err_no) + { + fprintf(stderr, "err_no 字段不存在\n"); + cJSON_Delete(json); + return NULL; + } + + // 判断err_no的值 + if (err_no->valueint != 0) + { + // 打印错误信息 + cJSON *err_msg = cJSON_GetObjectItem(json, "err_msg"); + if (err_msg) + { + fprintf(stderr, "err_msg: %s\n", err_msg->valuestring); + } + cJSON_Delete(json); + return NULL; + } + + // 获取 "result" 字段中的第一个元素 + cJSON *result = cJSON_GetObjectItem(json, "result"); + if (!result) + { + fprintf(stderr, "JSON 格式错误: 找不到'result' 字段\n"); + cJSON_Delete(json); + return NULL; + } + + if (cJSON_GetArraySize(result) > 0) + { + // 获取第一个元素的 "content" 字段 + cJSON *content = cJSON_GetArrayItem(result, 0); + if (content && cJSON_IsString(content)) + { + // 分配内存并返回字符串 + char *content_str = strdup(content->valuestring); + cJSON_Delete(json); + return content_str; + } + } + + // 释放 JSON 对象 + cJSON_Delete(json); + return NULL; +} + +int main(void) +{ + struct gpiod_chip *chip; + struct gpiod_line *line_key1, *line_key2, *line_key3; + int value_key1, last_value_key1; + int value_key2, last_value_key2; + int value_key3, last_value_key3; + + snd_pcm_uframes_t period = 999; // 周期大小 + snd_pcm_t *capture; // 音频采集设备 + char *buffer = NULL; // 存放音频数据的缓冲区 + FILE *fp = NULL; // 录音文件 + + + // 打开GPIO芯片 + chip = gpiod_chip_open_by_label("GPIOF"); + if (!chip) + { + perror("打开GPIO芯片失败"); + return 1; + } + + // 获取GPIO线 + line_key1 = gpiod_chip_get_line(chip, GPIO_LINE_KEY1); + line_key2 = gpiod_chip_get_line(chip, GPIO_LINE_KEY2); + line_key3 = gpiod_chip_get_line(chip, GPIO_LINE_KEY3); + if (!line_key1 || !line_key2 || !line_key3) + { + perror("获取GPIO线失败"); + gpiod_chip_close(chip); + return 1; + } + + // 将GPIO线设置为输入模式 + if (gpiod_line_request_input(line_key1, "key1") || + gpiod_line_request_input(line_key2, "key2") || + gpiod_line_request_input(line_key3, "key3")) + { + perror("请求将GPIO线设置为输入模式失败"); + gpiod_chip_close(chip); + return 1; + } + + // 获取初始的GPIO线值 + last_value_key1 = gpiod_line_get_value(line_key1); + last_value_key2 = gpiod_line_get_value(line_key2); + last_value_key3 = gpiod_line_get_value(line_key3); + printf("欢迎进入西理工图书馆导航系统!\n"); + get_and_print_poetry(); + // 无限循环检测GPIO线值的变化 + while (1) + { + // 获取当前的GPIO线值 + value_key1 = gpiod_line_get_value(line_key1); + value_key2 = gpiod_line_get_value(line_key2); + value_key3 = gpiod_line_get_value(line_key3); + + if (value_key1 != last_value_key1) + { + // 如果当前值为0,表示按键被按下 + if (value_key1 == 0) + { + printf("我在听👂\n\n"); + led_control("led1","1"); + capture = record_open("hw:0,1", SND_PCM_FORMAT_S16_LE, 1, 16000, &period); + if (!capture) + { + continue; + } + buffer = malloc(snd_pcm_frames_to_bytes(capture, period)); // 分配缓冲区 + if (!buffer) + { + perror("malloc"); + record_close(capture); + continue; + } + + fp = fopen("output.pcm", "wb"); + if (!fp) + { + perror("Error opening output file"); + free(buffer); // 释放缓冲区 + record_close(capture); // 关闭PCM设备 + continue; + } + } + // 如果当前值为1,表示按键被释放 + else + { + printf("好的,正在处理请稍候!\n"); + led_control("led1","0"); + record_close(capture); + capture = NULL; + + // 读取音频文件 + size_t size; + char *buffer = load_audio_file("output.pcm", &size); + if (!buffer) + { + return EXIT_FAILURE; + } + + // 读取配置信息,API KEY 和 SECRET KEY + cJSON *config = read_config("config.json"); + if (!config) + { + printf("config: %s\n", cJSON_Print(config)); + free(buffer); + return EXIT_FAILURE; + } + cJSON *api_key = cJSON_GetObjectItem(config, "api_key"); + cJSON *secret_key = cJSON_GetObjectItem(config, "secret_key"); + cJSON *authtoken = cJSON_GetObjectItem(config, "authtoken"); + if (!api_key || !secret_key || !authtoken) + { + fprintf(stderr, "配置文件错误: 找不到 'api_key' 或'secret_key'或'authtoken'字段\n"); + cJSON_Delete(config); + free(buffer); + return EXIT_FAILURE; + } + // 获取token + char *token = get_access_token(api_key->valuestring, secret_key->valuestring); + + if (!token) + { + fprintf(stderr, "获取 token 失败\n"); + free(buffer); + return EXIT_FAILURE; + } + + // 调用百度语音识别 API + char *response = send_request(token, buffer, size); + free(buffer); + free(token); + if (!response) + { + fprintf(stderr, "调用百度语音识别 API 失败\n"); + return EXIT_FAILURE; + } + // 处理服务器返回的响应消息 + char *text = process_response(response, size); + printf(">%s\n\n", text); + // printf("authtoken:%s\n", authtoken->valuestring); + // 创建会话 + if (!conv_id) + { + conv_id = create_conversation(authtoken->valuestring); + } + + // printf("conv_id: %s\n", conv_id); + char *answer = chat(authtoken->valuestring, conv_id, text); + printf("< %s\n", answer); + + text_to_speech(answer); + + cJSON_Delete(config); + free(response); + } + // 更新上一次的值为当前值 + last_value_key1 = value_key1; + } + + if (value_key1 == 0 && capture) + { + // 如果按键按下并且音频采集设备已打开 + snd_pcm_sframes_t frames = snd_pcm_readi(capture, buffer, period); // 从PCM设备读取数据 + if (frames < 0) + { + // fprintf(stderr, "Error from read: %s\n", snd_strerror(frames)); + snd_pcm_recover(capture, frames, 0); + } + + fwrite(buffer, snd_pcm_frames_to_bytes(capture, frames), 1, fp); // 将读取的数据写入文件 + } + + // 检测key2的变化 + if (value_key2 != last_value_key2) + { + if (value_key2 == 0) + { + printf("key2 pressed\n"); + led_control("led2","1"); + } + else + { + led_control("led2","0"); + long volume = get_playback_volume("hw:0", "Analog"); + volume += 5; + set_playback_volume("hw:0", "Analog", volume); + printf("Volume increased to %ld\n", volume); + } + last_value_key2 = value_key2; + } + + // 检测key3的变化 + if (value_key3 != last_value_key3) + { + if (value_key3 == 0) + { + printf("key3 pressed\n"); + led_control("led3","1"); + } + else + { + led_control("led3","0"); + long volume = get_playback_volume("hw:0", "Analog"); + volume -= 5; + set_playback_volume("hw:0", "Analog", volume); + printf("Volume decreased to %ld\n", volume); + } + last_value_key3 = value_key3; + } + + // 延时100毫秒,防止检测过于频繁 + // usleep(100000); + } + + // 关闭GPIO芯片 + gpiod_chip_close(chip); + return 0; +} diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/play.c" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/play.c" new file mode 100644 index 0000000..d49e2b8 --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/play.c" @@ -0,0 +1,150 @@ +#include +#include +#include +#include // 用于处理错误码 + +//开始播放 +snd_pcm_t* play_open(const char* name, + snd_pcm_format_t format, + unsigned int channel, + unsigned int rate, + snd_pcm_uframes_t* period) +{ + snd_pcm_t *playback; // PCM设备句柄 + snd_pcm_hw_params_t *params; // PCM硬件参数 + int err; // 用于存储错误码 + int dir; + + // 打开PCM设备用于回放 + if ((err = snd_pcm_open(&playback, name, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf(stderr, "Error opening PCM device %s: %s\n", name, snd_strerror(err)); + return NULL; + } + + // 分配参数对象,并用默认值填充 + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(playback, params); + + // 设置参数 + // 设置访问类型:交错模式 + if ((err = snd_pcm_hw_params_set_access(playback, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Error setting access: %s\n", snd_strerror(err)); + snd_pcm_close(playback); + return NULL; + } + // 设置数据格式:16位小端 + if ((err = snd_pcm_hw_params_set_format(playback, params, format)) < 0) { + fprintf(stderr, "Error setting format: %s\n", snd_strerror(err)); + snd_pcm_close(playback); + return NULL; + } + // 设置声道数:立体声 + if ((err = snd_pcm_hw_params_set_channels(playback, params, channel)) < 0) { + fprintf(stderr, "Error setting channels: %s\n", snd_strerror(err)); + snd_pcm_close(playback); + return NULL; + } + // 设置采样率 + if ((err = snd_pcm_hw_params_set_rate_near(playback, params, &rate, &dir)) < 0) { + fprintf(stderr, "Error setting rate: %s\n", snd_strerror(err)); + snd_pcm_close(playback); + return NULL; + } + + // printf("sample rate: %d Hz\n", rate); + + //设置周期大小 + if ((err = snd_pcm_hw_params_set_period_size_near(playback, params, period, &dir)) < 0) { + fprintf(stderr, "Error setting period size: %s\n", snd_strerror(err)); + snd_pcm_close(playback); + return NULL; + } + + // 设置硬件参数 + if ((err = snd_pcm_hw_params(playback, params)) < 0) { + fprintf(stderr, "Error setting HW params: %s\n", snd_strerror(err)); + snd_pcm_close(playback); + return NULL; + } + + // 获取周期大小 + snd_pcm_hw_params_get_period_size(params, period, &dir); + + return playback; +} + +//停止播放 +void play_close(snd_pcm_t* playback) +{ + snd_pcm_drain(playback); // 排空PCM设备 + snd_pcm_close(playback); // 关闭PCM设备 +} + +#if 0 +int main() +{ + snd_pcm_t *playback; // PCM设备句柄 + snd_pcm_uframes_t period = 999; // 每个周期的帧数 + char *buffer; // 缓冲区,用于存储从文件中读取的音频数据 + FILE *pcm_file; // 输出PCM文件 + int err; // 用于存储错误码 + + playback = play_open("hw:0,0", SND_PCM_FORMAT_S16_LE, 2, 44100, &period); + if (!playback) + { + return 1; + } + + printf("period: %d frames\n", period); + + buffer = (char *) malloc(snd_pcm_frames_to_bytes(playback, period)); // 分配缓冲区 + if (!buffer) { + perror("malloc"); + play_close(playback); + return 1; + } + + // 打开输出文件 + pcm_file = fopen("output.pcm", "rb"); + if (!pcm_file) { + perror("Error opening output file"); + free(buffer); // 释放缓冲区 + play_close(playback); // 关闭PCM设备 + return 1; + } + + // 录制数据 + printf("Playing... Press Ctrl+C to stop.\n"); + while (1) { + size_t bytes = fread(buffer, 1, snd_pcm_frames_to_bytes(playback, period), pcm_file); + if (bytes == 0) + { + if (ferror(pcm_file)) + { + perror("fread"); + continue; + } + + if (feof(pcm_file)) + { + break; + } + } + + snd_pcm_sframes_t frames = snd_pcm_writei(playback, buffer, snd_pcm_bytes_to_frames(playback, bytes)); + if (frames < 0) + { + fprintf(stderr, "Error from write: %s\n", snd_strerror(frames)); + snd_pcm_recover(playback, frames, 0); + } + } + + // 清理资源 + free(buffer); // 释放缓冲区 + fclose(pcm_file); // 关闭文件 + play_close(playback); + + return 0; +} + +#endif diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/play.h" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/play.h" new file mode 100644 index 0000000..5f67f8f --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/play.h" @@ -0,0 +1,14 @@ +#ifndef PLAY_H +#define PLAY_H + +#include + +snd_pcm_t* play_open(const char* name, + snd_pcm_format_t format, + unsigned int channel, + unsigned int rate, + snd_pcm_uframes_t* period); +//停止播放 +void play_close(snd_pcm_t* playback); + +#endif diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/record.c" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/record.c" new file mode 100644 index 0000000..653222c --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/record.c" @@ -0,0 +1,135 @@ +#include +#include +#include // 用于处理错误码 +#include "record.h" + +//开始录音 +snd_pcm_t* record_open(const char* name, + snd_pcm_format_t format, + unsigned int channel, + unsigned int rate, + snd_pcm_uframes_t* period) +{ + snd_pcm_t *capture; // PCM设备句柄 + snd_pcm_hw_params_t *params; // PCM硬件参数 + int err; // 用于存储错误码 + int dir; + + // 打开PCM设备用于录音(捕捉) + if ((err = snd_pcm_open(&capture, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) { + fprintf(stderr, "Error opening PCM device %s: %s\n", name, snd_strerror(err)); + return NULL; + } + + // 分配参数对象,并用默认值填充 + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(capture, params); + + // 设置参数 + // 设置访问类型:交错模式 + if ((err = snd_pcm_hw_params_set_access(capture, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Error setting access: %s\n", snd_strerror(err)); + snd_pcm_close(capture); + return NULL; + } + // 设置数据格式:16位小端 + if ((err = snd_pcm_hw_params_set_format(capture, params, format)) < 0) { + fprintf(stderr, "Error setting format: %s\n", snd_strerror(err)); + snd_pcm_close(capture); + return NULL; + } + // 设置声道数:立体声 + if ((err = snd_pcm_hw_params_set_channels(capture, params, channel)) < 0) { + fprintf(stderr, "Error setting channels: %s\n", snd_strerror(err)); + snd_pcm_close(capture); + return NULL; + } + // 设置采样率 + if ((err = snd_pcm_hw_params_set_rate_near(capture, params, &rate, &dir)) < 0) { + fprintf(stderr, "Error setting rate: %s\n", snd_strerror(err)); + snd_pcm_close(capture); + return NULL; + } + // printf("sample rate: %d Hz\n", rate); + + //设置周期大小 + if ((err = snd_pcm_hw_params_set_period_size_near(capture, params, period, &dir)) < 0) { + fprintf(stderr, "Error setting period size: %s\n", snd_strerror(err)); + snd_pcm_close(capture); + return NULL; + } + + // 设置硬件参数 + if ((err = snd_pcm_hw_params(capture, params)) < 0) { + fprintf(stderr, "Error setting HW params: %s\n", snd_strerror(err)); + snd_pcm_close(capture); + return NULL; + } + + // 获取周期大小 + snd_pcm_hw_params_get_period_size(params, period, &dir); + + return capture; +} + +//停止录音 +void record_close(snd_pcm_t* capture) +{ + snd_pcm_drain(capture); // 排空PCM设备 + snd_pcm_close(capture); // 关闭PCM设备 +} + +#if 0 +int main() +{ + snd_pcm_t *capture; // PCM设备句柄 + snd_pcm_uframes_t period; // 每个周期的帧数 + char *buffer; // 缓冲区,用于存储采集到的音频数据 + FILE *pcm_file; // 输出PCM文件 + int err; // 用于存储错误码 + + capture = record_open("hw:0,1", SND_PCM_FORMAT_S16_LE, 2, 44100, &period); + if (!capture) + { + return 1; + } + + printf("period: %d frames\n", period); + + buffer = (char *) malloc(snd_pcm_frames_to_bytes(capture, period)); // 分配缓冲区 + if (!buffer) { + perror("malloc"); + record_close(capture); + return 1; + } + + // 打开输出文件 + pcm_file = fopen("output.pcm", "wb"); + if (!pcm_file) { + perror("Error opening output file"); + free(buffer); // 释放缓冲区 + record_close(capture); // 关闭PCM设备 + return 1; + } + + // 录制数据 + printf("Recording... Press Ctrl+C to stop.\n"); + while (1) { + snd_pcm_sframes_t frames = snd_pcm_readi(capture, buffer, period); // 从PCM设备读取数据 + if (frames < 0) + { + fprintf(stderr, "Error from read: %s\n", snd_strerror(frames)); + snd_pcm_recover(capture, frames, 0); + } + + fwrite(buffer, snd_pcm_frames_to_bytes(capture, frames), 1, pcm_file); // 将读取的数据写入文件 + } + + // 清理资源 + free(buffer); // 释放缓冲区 + fclose(pcm_file); // 关闭文件 + record_close(capture); + + return 0; +} +#endif diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/record.h" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/record.h" new file mode 100644 index 0000000..6500100 --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/record.h" @@ -0,0 +1,16 @@ +#ifndef RECORD_H +#define RECORD_H + +#include + +//开始录音 +snd_pcm_t* record_open(const char* name, + snd_pcm_format_t format, + unsigned int channel, + unsigned int rate, + snd_pcm_uframes_t* period); + +//停止录音 +void record_close(snd_pcm_t* capture); + +#endif \ No newline at end of file diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/stt.c" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/stt.c" new file mode 100644 index 0000000..338e520 --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/stt.c" @@ -0,0 +1,153 @@ +#include +#include +#include //strdup +#include "config.h" +#include "token.h" +#include "http.h" +#include "stt.h" + +//读取音频文件 +//file: 音频文件路径 +//size: 音频文件大小 +//return: 音频文件内容, NULL 表示失败 +char* load_audio_file(const char* file, size_t* size) +{ + //打开音频文件 + FILE* fp = fopen(file, "rb"); + if (!fp) { + perror(file); + return NULL; + } + //获取文件大小 + fseek(fp, 0, SEEK_END); + *size = ftell(fp); + fseek(fp, 0, SEEK_SET); + //读取文件内容 + char* buffer = (char*)malloc(*size); + if (!buffer) { + perror("malloc"); + fclose(fp); + return NULL; + } + fread(buffer, 1, *size, fp); + fclose(fp); + return buffer; +} + +//发送请求消息 +//token: 获取的access token +//audio: 音频文件内容 +//size: 音频文件大小 +//return: 响应消息正文, NULL 表示失败 +static char* send_request(char* token, char* audio, size_t size) +{ + char* url = NULL; + asprintf(&url, "http://vop.baidu.com/server_api?cuid=hqyj&token=%s", token); + + struct curl_slist* headers = NULL; + headers = curl_slist_append(headers, "Content-Type: audio/pcm; rate=16000"); + + char* response = http_post(url, headers, audio, &size); + + free(url); + curl_slist_free_all(headers); + + return response; +} + +//处理服务器返回的响应消息 +static char* process_response(char* response) +{ + char* retval; + + //解析 JSON 响应 + cJSON *json = cJSON_Parse(response); + if (!json) { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + + //判断err_no字段 + cJSON* err_no = cJSON_GetObjectItem(json, "err_no"); + if (!err_no) { + fprintf(stderr, "err_no 字段不存在\n"); + cJSON_Delete(json); + return NULL; + } + //判断err_no的值 + if (err_no->valueint != 0) { + //打印错误信息 + cJSON* err_msg = cJSON_GetObjectItem(json, "err_msg"); + if (err_msg) + { + fprintf(stderr, "err_msg: %s\n", err_msg->valuestring); + } + cJSON_Delete(json); + return NULL; + } + + // 获取 "result" 字段中的第一个元素 + cJSON *result = cJSON_GetObjectItem(json, "result"); + if (!result) { + fprintf(stderr, "JSON 格式错误: 找不到'result' 字段\n"); + cJSON_Delete(json); + return NULL; + } + + if (cJSON_GetArraySize(result) > 0) { + // 获取第一个元素的 "content" 字段 + cJSON *content = cJSON_GetArrayItem(result, 0); + //保存结果 + retval = strdup(content->valuestring); + } + + cJSON_Delete(json); + + return retval; +} + +//将音频数据转换为字符串 +//audio: 存放音频数据的地址 +//size: 音频数据大小 +//返回值: 转换之后的字符串,转换失败返回NULL +char* speech_to_text(char* audio, size_t size) +{ + char* text; + //读取配置信息,API KEY 和 SECRET KEY + cJSON *config = read_config("config.json"); + if (!config) { + printf("config: %s\n", cJSON_Print(config)); + return NULL; + } + + cJSON* api_key = cJSON_GetObjectItem(config, "api_key"); + cJSON* secret_key = cJSON_GetObjectItem(config, "secret_key"); + if (!api_key ||!secret_key) { + fprintf(stderr, "配置文件错误: 找不到 'api_key' 或'secret_key' 字段\n"); + cJSON_Delete(config); + return NULL; + } + + //获取token + char* token = get_access_token(api_key->valuestring, secret_key->valuestring); + cJSON_Delete(config); + if (!token) { + fprintf(stderr, "获取 token 失败\n"); + return NULL; + } + + //调用百度语音识别 API + char* response = send_request(token, audio, size); + free(token); + if (!response) { + fprintf(stderr, "调用百度语音识别 API 失败\n"); + return NULL; + } + + //处理服务器返回的响应消息 + text = process_response(response); + + free(response); + + return text; +} diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/stt.h" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/stt.h" new file mode 100644 index 0000000..41ec6c4 --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/stt.h" @@ -0,0 +1,9 @@ +#ifndef STT_H +#define STT_H + +#include + +char* speech_to_text(char* audio, size_t size); + + +#endif diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/token.c" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/token.c" new file mode 100644 index 0000000..0aa9635 --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/token.c" @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include "http.h" +#include "config.h" +#include "token.h" + +//成功返回access token,失败返回NULL +char *get_access_token(const char *ak, const char *sk) +{ + char* token = NULL; + + //设置URL + char* url = "https://aip.baidubce.com/oauth/2.0/token"; + char* form = NULL; + asprintf(&form, "grant_type=client_credentials&client_id=%s&client_secret=%s&", ak, sk); + + //发送请求报文 + size_t size = strlen(form); + char* response = http_post(url, NULL, form, &size); + free(form); + + if (!response) + { + return NULL; + } + + //解析响应报文 + cJSON* root = cJSON_ParseWithLength(response, size); + free(response); + + if (!root) + { + // 解析错误 + const char *error_ptr = cJSON_GetErrorPtr(); + if (error_ptr != NULL) + { + fprintf(stderr, "Error before: %s\n", error_ptr); + } + return NULL; + } + + cJSON* access_token = cJSON_GetObjectItem(root, "access_token"); + if (!access_token) + { + fprintf(stderr, "access_token attribute not found\n"); + cJSON_Delete(root); + return NULL; + } + + if (!cJSON_IsString(access_token)) + { + fprintf(stderr, "access_token attribute format error\n"); + cJSON_Delete(root); + return NULL; + } + + token = strdup(access_token->valuestring); + + // 删除解析后对象占用的内存 + cJSON_Delete(root); + + return token; +} + diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/token.h" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/token.h" new file mode 100644 index 0000000..5b2442b --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/token.h" @@ -0,0 +1,7 @@ +#ifndef TOKEN_H +#define TOKEN_H + +//成功返回access token,失败返回NULL +char *get_access_token(const char *ak, const char *sk); + +#endif diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/tts.c" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/tts.c" new file mode 100644 index 0000000..fd22d9d --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/tts.c" @@ -0,0 +1,227 @@ +#include "tts.h" +#include +#include +#include +#include +#include +#include "config.h" +#include "http.h" +#include "play.h" + +char *appid = "6427208609"; + +// 生成UUID +char *gen_uuid(void) +{ + static char uuid_str[37]; + uuid_t uuid; + uuid_generate(uuid); + uuid_unparse(uuid, uuid_str); + return uuid_str; +} + +// Base64解码 +size_t base64_decode(const char *base64, char **decoded) +{ + size_t decoded_size = (strlen(base64) / 4 + 1) * 3; + *decoded = (char *)malloc(decoded_size); + if (!*decoded) + { + return 0; + } + + int size = b64_pton(base64, *decoded, decoded_size); + if (size < 0) + { + return 0; + } + + return size; +} + +// 发送请求 +// 发送请求 +char *tts_send_request(char *text) +{ + cJSON *config = read_config("config.json"); + if (!config) + { + return NULL; + } + + cJSON *ttstoken = cJSON_GetObjectItem(config, "ttstoken"); + if (!ttstoken) + { + fprintf(stderr, "无法获取ttstoken\n"); + return NULL; + } + + char *url = "https://openspeech.bytedance.com/api/v1/tts"; + + char *auth; + asprintf(&auth, "Authorization: Bearer;%s", ttstoken->valuestring); + + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Content-Type: application/json"); + headers = curl_slist_append(headers, auth); + free(auth); + + cJSON *obj = cJSON_CreateObject(); + cJSON *app = cJSON_CreateObject(); + cJSON_AddStringToObject(app, "appid", appid); + cJSON_AddStringToObject(app, "token", ttstoken->valuestring); + cJSON_AddStringToObject(app, "cluster", "volcano_tts"); + cJSON_AddItemToObject(obj, "app", app); + + cJSON *user = cJSON_CreateObject(); + cJSON_AddStringToObject(user, "uid", "hqyj"); + cJSON_AddItemToObject(obj, "user", user); + + cJSON *audio = cJSON_CreateObject(); + cJSON_AddStringToObject(audio, "voice_type", "BV700_V2_streaming"); + cJSON_AddStringToObject(audio, "speed_ratio", "1.2"); + cJSON_AddStringToObject(audio, "rate", "16000"); + cJSON_AddItemToObject(obj, "audio", audio); + + cJSON *request = cJSON_CreateObject(); + cJSON_AddStringToObject(request, "reqid", gen_uuid()); + cJSON_AddStringToObject(request, "text", text); + cJSON_AddStringToObject(request, "operation", "query"); + cJSON_AddItemToObject(obj, "request", request); + + char *json = cJSON_Print(obj); + size_t size = strlen(json); + cJSON_Delete(obj); + + return http_post(url, headers, json, &size); +} + +// 处理服务器返回的响应消息 +char *tts_process_response(char *response, size_t *size) +{ + cJSON *obj = cJSON_Parse(response); + if (!obj) + { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + + cJSON *code = cJSON_GetObjectItem(obj, "code"); + if (!code) + { + fprintf(stderr, "JSON 格式错误: 找不到 'code' 字段\n"); + return NULL; + } + + if (code->valueint != 3000) + { + cJSON *message = cJSON_GetObjectItem(obj, "message"); + if (message) + { + fprintf(stderr, "message: %s\n", message->valuestring); + } + return NULL; + } + + cJSON *data = cJSON_GetObjectItem(obj, "data"); + if (!data) + { + fprintf(stderr, "JSON 格式错误: 找不到 'data' 字段\n"); + return NULL; + } + + char *audio = NULL; + size_t audio_size = base64_decode(data->valuestring, &audio); + if (audio_size == 0) + { + fprintf(stderr, "base64解码失败\n"); + return NULL; + } + + cJSON_Delete(obj); + + *size = audio_size; + return audio; +} + +// 语音合成 +void text_to_speech(char *text) +{ + char *response = tts_send_request(text); + if (!response) + { + fprintf(stderr, "调用语音合成 API 失败\n"); + return; + } + + size_t size = 0; + char *audio = tts_process_response(response, &size); + if (!audio) + { + + return; + } + + snd_pcm_t *playback; + snd_pcm_uframes_t period = 999; + char *buffer; + FILE *pcm_file; + int err; + + pcm_file = fmemopen(audio, size, "rb"); + if (!pcm_file) + { + perror("Error opening output file"); + return; + } + + playback = play_open("hw:0,0", SND_PCM_FORMAT_S16_LE, 1, 16000, &period); + if (!playback) + { + fclose(pcm_file); + return; + } + + // printf("period: %lu frames\n", period); + + buffer = (char *)malloc(snd_pcm_frames_to_bytes(playback, period)); + if (!buffer) + { + perror("malloc"); + play_close(playback); + fclose(pcm_file); + return; + } + + // 录制数据 + while (1) + { + size_t bytes = fread(buffer, 1, snd_pcm_frames_to_bytes(playback, period), pcm_file); + if (bytes == 0) + { + if (ferror(pcm_file)) + { + perror("fread"); + continue; + } + + if (feof(pcm_file)) + { + break; + } + } + + snd_pcm_sframes_t frames = snd_pcm_writei(playback, buffer, snd_pcm_bytes_to_frames(playback, bytes)); + if (frames < 0) + { + // fprintf(stderr, "Error from write: %s\n", snd_strerror(frames)); + snd_pcm_recover(playback, frames, 0); + } + } + + free(buffer); + fclose(pcm_file); + play_close(playback); + + free(response); +} diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/tts.h" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/tts.h" new file mode 100644 index 0000000..4e6594d --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/tts.h" @@ -0,0 +1,21 @@ +#ifndef TTS_H +#define TTS_H + +#include + +// 生成UUID +char* gen_uuid(void); + +// Base64解码 +size_t base64_decode(const char* base64, char** decoded); + +// 发送请求 +char* tts_send_request(char* text); + +// 处理服务器返回的响应消息 +char* tts_process_response(char* response, size_t* size); + +// 语音合成 +void text_to_speech(char* text); + +#endif // TTS_H diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/utils.c" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/utils.c" new file mode 100644 index 0000000..08afcc8 --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/utils.c" @@ -0,0 +1,36 @@ +#include +#include +#include //b64_pton +#include + +// 生成uuid +char* gen_uuid(void){ + static char uuid_str[37]; + uuid_t uuid; + uuid_generate(uuid); + uuid_unparse(uuid, uuid_str); + return uuid_str; +} + +// base64解码 +// base64:需要解码的base64字符串 +// decoded: 解码后的数字 +// 返回值:解码后的大小 +size_t base64_decode(const char* base64,char* decoded){ + // 计算解码后的大小 + size_t decoded_size = (strlen(base64) / 4) * 3; + int size = b64_pton(base64, decoded, decoded_size); + if(size<0){ + return 0; + } + return size; +} + + +int main(){ + puts(gen_uuid()); + char text[100]; + int size = base64_decode("aGVsbG8sIHdvcmxk",text); + printf("%s,%d\n",text,size); + return 0; +} \ No newline at end of file diff --git "a/4/\351\241\271\347\233\256\344\273\243\347\240\201/wake.c" "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/wake.c" new file mode 100644 index 0000000..d83e767 --- /dev/null +++ "b/4/\351\241\271\347\233\256\344\273\243\347\240\201/wake.c" @@ -0,0 +1,324 @@ +#include "snowboy/snowboy-detect-c-wrapper.h" +#include +#include +#include +#include "record.h" +#include "stt.h" +#include "tts.h" +#include "jrsc.h" +#include "config.h" +#include "http.h" +char *app_id = "62b84ef5-5daf-491d-97a8-5966fb4deb34"; +char *conv_id = NULL; + +// led控制 +void led_control(char *led, char *status) +{ + char file_path[50] = "/sys/class/leds/"; + sprintf(file_path, "%s%s%s", file_path, led, "/brightness"); + FILE *fp = fopen(file_path, "w"); + if (fp == NULL) + { + perror("打开LED设备文件失败"); + return; + } + fprintf(fp, status); + fflush(fp); + // 关闭LED设备文件 + fclose(fp); +} +// 创建会话 +char *create_conversation(char *authtoken) +{ + char *url = "https://qianfan.baidubce.com/v2/app/conversation"; + + // 拼接Authorization字段 + char *auth; + asprintf(&auth, "Authorization: Bearer %s", authtoken); + + // 添加请求头部字段 + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Content-Type: application/json;charset=utf-8"); + headers = curl_slist_append(headers, auth); + + // 准备请求正文 + cJSON *obj = cJSON_CreateObject(); + cJSON_AddStringToObject(obj, "app_id", app_id); + // 将JSON对象转换为JSON字符串 + char *json = cJSON_Print(obj); + + // 发送请求 + size_t size = strlen(json); + char *response = http_post(url, headers, json, &size); + cJSON_Delete(obj); + free(json); + free(auth); + curl_slist_free_all(headers); + + if (!response) + { + return NULL; + } + + obj = cJSON_ParseWithLength(response, size); + free(response); + + if (!obj) + { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + + cJSON *conversation_id = cJSON_GetObjectItem(obj, "conversation_id"); + if (!conversation_id) + { + fprintf(stderr, "conversation_id 字段不存在\n"); + cJSON_Delete(obj); + return NULL; + } + + char *retval = strdup(conversation_id->valuestring); + + cJSON_Delete(obj); + + return retval; +} + +// 调用对话API +// authtoken: 平台密钥 +// conv_id: 会话ID +// query: 用户输入的文本 +// 返回值: 对话结果 +char *chat(char *authtoken, char *conv_id, char *query) +{ + char *url = "https://qianfan.baidubce.com/v2/app/conversation/runs"; + + // 拼接Authorization字段 + char *auth; + asprintf(&auth, "Authorization: Bearer %s", authtoken); + + // 设置请求头部字段 + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Content-Type: application/json;charset=utf-8"); + headers = curl_slist_append(headers, auth); + + // 准备请求正文 + cJSON *obj = cJSON_CreateObject(); + cJSON_AddStringToObject(obj, "app_id", app_id); + cJSON_AddStringToObject(obj, "conversation_id", conv_id); + cJSON_AddStringToObject(obj, "query", query); + cJSON_AddBoolToObject(obj, "stream", false); + // 将JSON对象转换为JSON字符串 + char *json = cJSON_Print(obj); + + // 发送请求 + size_t size = strlen(json); + char *response = http_post(url, headers, json, &size); + cJSON_Delete(obj); + free(json); + curl_slist_free_all(headers); + free(auth); + + if (!response) + { + return NULL; + } + + obj = cJSON_ParseWithLength(response, size); + free(response); + + if (!obj) + { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + + cJSON *answer = cJSON_GetObjectItem(obj, "answer"); + if (!answer) + { + fprintf(stderr, "answer 字段不存在\n"); + puts(cJSON_Print(obj)); + cJSON_Delete(obj); + return NULL; + } + + char *retval = strdup(answer->valuestring); + + cJSON_Delete(obj); + + return retval; +} + + +int main() +{ + // 创建snowboy检测器 + SnowboyDetect *detector = SnowboyDetectConstructor("common.res", "model.pmdl"); + if (!detector) + { + return EXIT_FAILURE; + } + + // 获取检测器支持的音频数据参数 + // 采样深度 + int bits = SnowboyDetectBitsPerSample(detector); + // 声道数量 + int channels = SnowboyDetectNumChannels(detector); + // 采样频率 + int rate = SnowboyDetectSampleRate(detector); + + printf("采样深度: %d\n", bits); + printf("声道数量: %d\n", channels); + printf("采样频率: %d\n", rate); + + // 打开音频采集设备 + snd_pcm_uframes_t period = 999; + snd_pcm_t *capture = record_open("hw:0,0", SND_PCM_FORMAT_S16_LE, channels, rate, &period); + if (!capture) + { + return EXIT_FAILURE; + } + + char *buffer = malloc(snd_pcm_frames_to_bytes(capture, period)); // 分配缓冲区 + if (!buffer) + { + perror("malloc"); + record_close(capture); + SnowboyDetectDestructor(detector); + return EXIT_FAILURE; + } + + int recording = 0; + // 检测到连续的静音次数 + int silence = 0; + // 内存文件 + FILE *memstream = NULL; + char *audio = NULL; + size_t audio_size = 0; + + while (1) + { + snd_pcm_sframes_t frames = snd_pcm_readi(capture, buffer, period); // 从PCM设备读取数据 + if (frames < 0) + { + // fprintf(stderr, "Error from read: %s\n", snd_strerror(frames)); + snd_pcm_recover(capture, frames, 0); + continue; + } + + //-2: 静音 + //-1: 检测出错 + // 0: 有声音,但不是唤醒词 + //>0: 检测到唤醒词 + int status = SnowboyDetectRunDetection(detector, (int16_t *)buffer, snd_pcm_frames_to_bytes(capture, frames) / sizeof(int16_t), 0); + if (status > 0) + { + printf("在呢在呢,请说👂\n\n"); + // 录音时led3亮,其他灭 + led_control("led3", "1"); + led_control("led1", "1"); + led_control("led2", "1"); + recording = 1; + // 打开内存文件 + memstream = open_memstream(&audio, &audio_size); + if (!memstream) + { + perror("open_memstream"); + continue; + } + } + if (status == -2) + { + // 静音时led1亮 + led_control("led1", "1"); + led_control("led2", "0"); + led_control("led3", "0"); + } + if (status == 0) + { + // 有声音,不是唤醒词led2亮,其他灯灭 + led_control("led2", "1"); + led_control("led1", "0"); + led_control("led3", "0"); + } + if (recording) + { + if (status == -2) + { + silence++; + } + + if (status == 0) + { + silence = 0; + } + + if (silence > 48) + { + printf("好的,正在处理请稍候!\n"); + // led_control("led3", "0"); + // led_control("led1", "0"); + // led_control("led2", "0"); + recording = 0; + silence = 0; + if (memstream) + { + fclose(memstream); + memstream = NULL; + } + + if (audio_size) + { + // 暂停录音 + snd_pcm_drop(capture); + // 识别语音 + char *text = speech_to_text(audio, audio_size); + if (text) + { + printf(">%s\n\n", text); + } + + // 读取配置文件中的平台密钥 + cJSON *config = read_config("config.json"); + if (!config) + { + return EXIT_FAILURE; + } + + cJSON *authtoken = cJSON_GetObjectItem(config, "authtoken"); + if (!authtoken) + { + fprintf(stderr, "配置文件错误: 找不到 'authtoken' 字段\n"); + cJSON_Delete(config); + return EXIT_FAILURE; + } + // printf("%s\n", authtoken->valuestring); + // 创建会话 + if (!conv_id) + { + conv_id = create_conversation(authtoken->valuestring); + } + // printf("conv_id: %s\n", conv_id); + char *answer = chat(authtoken->valuestring, conv_id, text); + printf("< %s\n", answer); + + text_to_speech(answer); + + // 恢复录音 + snd_pcm_prepare(capture); + } + } + + if (memstream) + { + fwrite(buffer, 1, snd_pcm_frames_to_bytes(capture, frames), memstream); + } + } + } + + free(buffer); + record_close(capture); + SnowboyDetectDestructor(detector); + + return EXIT_SUCCESS; +} \ No newline at end of file -- Gitee