diff --git a/5/allcode/CMakePresets.json b/5/allcode/CMakePresets.json new file mode 100644 index 0000000000000000000000000000000000000000..19a5c33e29b8c908143f57c998afaf316d7a7915 --- /dev/null +++ b/5/allcode/CMakePresets.json @@ -0,0 +1,29 @@ +{ + "version": 8, + "configurePresets": [ + { + "name": "native", + "displayName": "GCC 10.2.1 x86_64-linux-gnu", + "description": "使用编译器: C = /usr/bin/gcc, CXX = /usr/bin/g++", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "CMAKE_C_COMPILER": "/usr/bin/gcc", + "CMAKE_CXX_COMPILER": "/usr/bin/g++", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "arm", + "displayName": "GCC 10.2.1 arm-linux-gnueabihf", + "description": "使用编译器: C = /usr/bin/arm-linux-gnueabihf-gcc, CXX = /usr/bin/arm-linux-gnueabihf-g++", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "cacheVariables": { + "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", + "CMAKE_C_COMPILER": "/usr/bin/arm-linux-gnueabihf-gcc", + "CMAKE_CXX_COMPILER": "/usr/bin/arm-linux-gnueabihf-g++", + "CMAKE_BUILD_TYPE": "Debug" + } + } + ] +} \ No newline at end of file diff --git a/5/allcode/chat.c b/5/allcode/chat.c new file mode 100644 index 0000000000000000000000000000000000000000..b315fa280b53a8421e237c141535e7e7bcc6d83b --- /dev/null +++ b/5/allcode/chat.c @@ -0,0 +1,200 @@ +//调用百度云千帆AppBuilder API进行对话 + +#include +#include +#include +#include +#include "config.h" +#include "http.h" +#include "chat.h" + +char* app_id = "f221d824-876c-42bc-b6c5-4f1581ea0f96"; + +//创建会话 +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 = 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* query) +{ + char* url = "https://qianfan.baidubce.com/v2/app/conversation/runs"; + + //读取配置文件中的平台密钥 + cJSON* config = read_config("config.json"); + if (!config) + { + return NULL; + } + + cJSON* authtoken = cJSON_GetObjectItem(config, "authtoken"); + if (!authtoken) + { + fprintf(stderr, "配置文件错误: 找不到 'authtoken' 字段\n"); + cJSON_Delete(config); + return NULL; + } + + //创建会话 + char* conv_id = create_conversation(authtoken->valuestring); + + //拼接Authorization字段 + char* auth; + asprintf(&auth, "Authorization: Bearer %s", authtoken->valuestring); + + //设置请求头部字段 + 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 = 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; +} + +#if 0 +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; +} +#endif diff --git a/5/allcode/chat.h b/5/allcode/chat.h new file mode 100644 index 0000000000000000000000000000000000000000..fdc8953435001997c168125ede9b0f0712557088 --- /dev/null +++ b/5/allcode/chat.h @@ -0,0 +1,8 @@ +#ifndef CHAT_H +#define CHAT_H + +char* create_conversation(char* authtoken); + +char* chat(char* query); + +#endif \ No newline at end of file diff --git a/5/allcode/config.c b/5/allcode/config.c new file mode 100644 index 0000000000000000000000000000000000000000..c7b7973533472f422f41350985b09887bc1aaea0 --- /dev/null +++ b/5/allcode/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/5/allcode/config.h b/5/allcode/config.h new file mode 100644 index 0000000000000000000000000000000000000000..fd3546b11f4adf5e34488295d9af4191c01f164f --- /dev/null +++ b/5/allcode/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/5/allcode/config.json b/5/allcode/config.json new file mode 100644 index 0000000000000000000000000000000000000000..ee580c737b1e6129d89ae9ef273c3e85625952f9 --- /dev/null +++ b/5/allcode/config.json @@ -0,0 +1,6 @@ +{ + "api_key":"27mXtj0Hg2d0GbRQUNyJ11o6", + "secret_key":"jJiFfYLHvTLpnJzQqF3TZ8RXiOmHsaBk", + "authtoken":"bce-v3/ALTAK-u06wWROuOXRcwJdOR3YGN/39cd0227231afd7db261f126f7ae15b03a949586", + "ttstoken":"opehGvCtyT7DAQZZM6CRrC9-7aDwQkkd" +} \ No newline at end of file diff --git a/5/allcode/control.c b/5/allcode/control.c new file mode 100644 index 0000000000000000000000000000000000000000..c7f9306f088130d5767398984a6379f6e8a64f7b --- /dev/null +++ b/5/allcode/control.c @@ -0,0 +1,505 @@ +#include +#include +#include +#include "control.h" + +// 设置音频回放开关 +// card:声卡名称 +// selem:控制项名称 +// enable:开关状态 +// 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'\n", selem); + 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; // 成功 +} + +// 设置音频采集开关 +// card:声卡名称 +// selem:控制项名称 +// enable:开关状态 +// return:成功返回0,失败返回错误码 +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; // 成功 +} + +// 获取音频回放音量 +// card:声卡名称 +// selem:控制项名称 +// return:当前音量 +int get_playback_volume(const char *card, const char *selem) +{ + int err; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + long volume = 0; + + // 打开混音器 + 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'\n", selem); + snd_mixer_close(handle); + return -ENOENT; + } + + // 获取回放通道音量 + 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; // 成功 +} + +// 获取音频采集音量 +// card:声卡名称 +// selem:控制项名称 +// return:当前音量 +int get_capture_volume(const char *card, const char *selem) +{ + int err; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + long volume = 0; + + // 打开混音器 + 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'\n", selem); + snd_mixer_close(handle); + return -ENOENT; + } + + // 获取采集通道音量 + 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; // 成功返回音量 +} + +// 设置音频回放音量 +// card:声卡名称 +// selem:控制项名称 +// volume:音量 +// return:成功返回设置后的音量,失败返回错误码 +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'\n", selem); + snd_mixer_close(handle); + return -ENOENT; + } + + // 获取音量范围 + long min, max; + if ((err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max)) < 0) + { + fprintf(stderr, "Unable to get playback volume range '%s'\n", selem); + snd_mixer_close(handle); + } + + if (volume < min) + { + volume = min; + } + + if (volume > max) + { + volume = max; + } + + // 设置回放通道音量 + 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; // 成功 +} + +// 设置音频采集音量 +// card:声卡名称 +// selem:控制项名称 +// volume:音量 +// return:成功返回设置后的音量值,失败返回错误码 +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'\n", selem); + snd_mixer_close(handle); + return -ENOENT; + } + + // 获取音量范围 + long min, max; + if ((err = snd_mixer_selem_get_capture_volume_range(elem, &min, &max)) < 0) + { + fprintf(stderr, "Unable to get capture volume range '%s'\n", selem); + snd_mixer_close(handle); + return err; + } + if (volume < min) + { + volume = min; + } + + if (volume > max) + { + volume = max; + } + + // 设置采集通道音量 + 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 main(int argc, char **argv) +// { +// if (strcmp(argv[1], "in-on") == 0) +// { +// set_capture_switch("hw:0", "Mic Boost", true); +// } + +// if (strcmp(argv[1], "in-off") == 0) +// { +// set_capture_switch("hw:0", "Mic Boost", false); +// } + +// if (strcmp(argv[1], "out-on") == 0) +// { +// set_playback_switch("hw:0", "Analog", true); +// //set_playback_switch("hw:0", "PCM", true); +// } + +// if (strcmp(argv[1], "out-off") == 0) +// { +// set_playback_switch("hw:0", "Analog", false); +// //set_playback_switch("hw:0", "PCM", false); +// } + +// if (strcmp(argv[1], "set-in") == 0) +// { +// printf("Capture Vol: %d\n", get_capture_volume("hw:0", "Mic Boost")); +// printf("New Capture Vol: %d\n", set_capture_volume("hw:0", "Mic Boost", atoi(argv[2]))); +// } + +// if (strcmp(argv[1], "set-out") == 0) +// { +// printf("Playback Vol: %d\n", get_playback_volume("hw:0", "Analog")); +// printf("New Playback Vol: %d\n", set_playback_volume("hw:0", "Analog", atoi(argv[2]))); +// } + +// return 0; +// } \ No newline at end of file diff --git a/5/allcode/control.h b/5/allcode/control.h new file mode 100644 index 0000000000000000000000000000000000000000..a0631ea14277ae4358408e6d9307549b3ef9f9d5 --- /dev/null +++ b/5/allcode/control.h @@ -0,0 +1,46 @@ +#ifndef CONTROL_H +#define CONTROL_H + +#include + +// 设置音频回放开关 +// card:声卡名称 +// selem:控制项名称 +// enable:开关状态 +// return:成功返回0,失败返回错误码 +int set_playback_switch(const char *card, const char *selem, bool enable); + +// 设置音频采集开关 +// card:声卡名称 +// selem:控制项名称 +// enable:开关状态 +// return:成功返回0,失败返回错误码 +int set_capture_switch(const char *card, const char *selem, bool enable); + +// 获取音频回放音量 +// card:声卡名称 +// selem:控制项名称 +// return:当前音量 +int get_playback_volume(const char *card, const char *selem); + +// 获取音频采集音量 +// card:声卡名称 +// selem:控制项名称 +// return:当前音量 +int get_capture_volume(const char *card, const char *selem); + +// 设置音频回放音量 +// card:声卡名称 +// selem:控制项名称 +// volume:音量 +// return:成功返回设置后的音量,失败返回错误码 +int set_playback_volume(const char *card, const char *selem, long volume); + +// 设置音频采集音量 +// card:声卡名称 +// selem:控制项名称 +// volume:音量 +// return:成功返回设置后的音量值,失败返回错误码 +int set_capture_volume(const char *card, const char *selem, long volume); + +#endif \ No newline at end of file diff --git a/5/allcode/http.c b/5/allcode/http.c new file mode 100644 index 0000000000000000000000000000000000000000..87a0595ce78a6ddcc4d3ea2e146d87c0c8520e63 --- /dev/null +++ b/5/allcode/http.c @@ -0,0 +1,160 @@ +#include +#include "http.h" + +//发送GET请求 +//url: 请求地址 +//headers: 增加的请求头部字段 +//size: 响应正文大小,输出参数 +//返回值: 响应正文,需要调用者释放内存 +char* 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: 请求正文 +//size: 请求和响应正文大小,输入和输出参数 +//返回值: 响应正文,需要调用者释放内存 +char* 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; +} + +// 发送邮件 +int send_email(const char *url, const char *json_data) +{ + CURL *curl; + CURLcode res; + int result = 0; + + // Initialize CURL + curl_global_init(CURL_GLOBAL_DEFAULT); + curl = curl_easy_init(); + + if (curl) + { + // Set the URL for the POST request + curl_easy_setopt(curl, CURLOPT_URL, url); + + // Set the POST fields + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data); + + // Set the headers for the POST request + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Content-Type: application/json"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + // Perform the request, res will get the return code + res = curl_easy_perform(curl); + + // Check for errors + if (res != CURLE_OK) + { + fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); + result = 1; + } + + // Cleanup + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + } + else + { + fprintf(stderr, "Failed to initialize CURL\n"); + result = 1; + } + + // Cleanup CURL globally + curl_global_cleanup(); + + return result; +} \ No newline at end of file diff --git a/5/allcode/http.h b/5/allcode/http.h new file mode 100644 index 0000000000000000000000000000000000000000..2348cadc16e37e2e61587d27520575a685d0fd53 --- /dev/null +++ b/5/allcode/http.h @@ -0,0 +1,12 @@ +#ifndef HTTP_H +#define HTTP_H + +#include + +char* get(char* url, struct curl_slist* headers, size_t* psize); + +char* post(char* url, struct curl_slist* headers, char* data, size_t* psize); + +int send_email(const char *url, const char *json_data); + +#endif \ No newline at end of file diff --git a/5/allcode/jrsc.c b/5/allcode/jrsc.c new file mode 100644 index 0000000000000000000000000000000000000000..62f6bbc40e99810f7444d63080d1d59091a2fa26 --- /dev/null +++ b/5/allcode/jrsc.c @@ -0,0 +1,200 @@ +#include +#include +#include +#include + +// 打印推荐的古诗词 +void print_recommended_poetry(const char *response, size_t size) +{ + // 解析 JSON 响应 + cJSON *json = cJSON_ParseWithLength(response, size); + if (json == NULL) + { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return; + } + + // 获取 "data" 对象 + cJSON *data = cJSON_GetObjectItemCaseSensitive(json, "data"); + if (!cJSON_IsObject(data)) + { + fprintf(stderr, "JSON 格式错误: 找不到 'data' 对象\n"); + cJSON_Delete(json); + return; + } + + // 获取 "content" 字符串 + 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); + + // 释放 JSON 对象 + cJSON_Delete(json); +} + +// 打印天气信息 +void print_weather_info(const char *response, size_t size) +{ + // 解析 JSON 响应 + cJSON *json = cJSON_ParseWithLength(response, size); + if (json == NULL) + { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return; + } + + // 获取 "data" 对象 + cJSON *data = cJSON_GetObjectItemCaseSensitive(json, "data"); + if (!cJSON_IsObject(data)) + { + fprintf(stderr, "JSON 格式错误: 找不到 'data' 对象\n"); + cJSON_Delete(json); + return; + } + + // 获取ip和地区 + cJSON *ip = cJSON_GetObjectItemCaseSensitive(data, "ip"); + if (!cJSON_IsString(ip)) + { + fprintf(stderr, "JSON 格式错误: 找不到 'ip' 对象\n"); + cJSON_Delete(json); + return; + } + + cJSON *region = cJSON_GetObjectItemCaseSensitive(data, "region"); + if (!cJSON_IsString(region)) + { + fprintf(stderr, "JSON 格式错误: 找不到 'region' 对象\n"); + cJSON_Delete(json); + return; + } + + // 获取 "weatherData" 对象 + cJSON *weatherData = cJSON_GetObjectItemCaseSensitive(data, "weatherData"); + if (!cJSON_IsObject(weatherData)) + { + fprintf(stderr, "JSON 格式错误: 找不到 'weatherData' 对象\n"); + cJSON_Delete(json); + return; + } + + // 获取并打印天气信息 + cJSON *temperature = cJSON_GetObjectItemCaseSensitive(weatherData, "temperature"); + cJSON *windDirection = cJSON_GetObjectItemCaseSensitive(weatherData, "windDirection"); + cJSON *windPower = cJSON_GetObjectItemCaseSensitive(weatherData, "windPower"); + cJSON *humidity = cJSON_GetObjectItemCaseSensitive(weatherData, "humidity"); + cJSON *updateTime = cJSON_GetObjectItemCaseSensitive(weatherData, "updateTime"); + cJSON *weather = cJSON_GetObjectItemCaseSensitive(weatherData, "weather"); + cJSON *visibility = cJSON_GetObjectItemCaseSensitive(weatherData, "visibility"); + cJSON *rainfall = cJSON_GetObjectItemCaseSensitive(weatherData, "rainfall"); + cJSON *pm25 = cJSON_GetObjectItemCaseSensitive(weatherData, "pm25"); + + // 检查指针并打印信息 + if (cJSON_IsString(region) && region->valuestring) + { + printf("地区:%s\n", region->valuestring); + } + if (cJSON_IsString(ip) && ip->valuestring) + { + printf("IP:%s\n", ip->valuestring); + } + if (cJSON_IsNumber(temperature)) + { + printf("当前温度:%d °C\n", temperature->valueint); + } + if (cJSON_IsString(windDirection) && windDirection->valuestring) + { + printf("风向:%s\n", windDirection->valuestring); + } + if (cJSON_IsNumber(windPower)) + { + printf("风力等级:%d\n", windPower->valueint); + } + if (cJSON_IsNumber(humidity)) + { + printf("相对湿度:%d%%\n", humidity->valueint); + } + if (cJSON_IsString(updateTime) && updateTime->valuestring) + { + printf("更新时间:%s\n", updateTime->valuestring); + } + if (cJSON_IsString(weather) && weather->valuestring) + { + printf("天气状况:%s\n", weather->valuestring); + } + if (cJSON_IsString(visibility) && visibility->valuestring) + { + printf("能见度:%s\n", visibility->valuestring); + } + if (cJSON_IsNumber(rainfall)) + { + printf("降雨量:%dmm\n", rainfall->valueint); + } + if (cJSON_IsNumber(pm25)) + { + printf("PM2.5指数:%d\n", pm25->valueint); + } + + // 释放 JSON 对象 + cJSON_Delete(json); +} + +int main(void) +{ + CURL *client; + CURLcode err; + char *response; + size_t size; + + // 创建内存流 + FILE *memstream = open_memstream(&response, &size); + if (memstream == NULL) + { + perror("open_memstream"); + return 1; + } + + // 初始化 CURL + curl_global_init(CURL_GLOBAL_ALL); + client = curl_easy_init(); + + // 设置 CURL 选项 + // curl_easy_setopt(client, CURLOPT_URL, "https://v2.jinrishici.com/sentence"); + curl_easy_setopt(client, CURLOPT_URL, "https://v2.jinrishici.com/info"); + curl_easy_setopt(client, CURLOPT_WRITEDATA, memstream); + + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "X-User-Token:nV1uhd7FCrre5VzS9IzVAOvPjC/jBg/w"); // o9M10gtneoIxRLk5UO13fUG7guYxnS7h + curl_easy_setopt(client, CURLOPT_HTTPHEADER, headers); + + // 执行 CURL 请求 + 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); + // 打印天气信息 + print_weather_info(response, size); + } + + // 清理资源 + curl_slist_free_all(headers); + curl_easy_cleanup(client); + free(response); + curl_global_cleanup(); + + return 0; +} \ No newline at end of file diff --git a/5/allcode/key.c b/5/allcode/key.c new file mode 100644 index 0000000000000000000000000000000000000000..da8240a92168c1054a82bd6f5954efdd3e8b5186 --- /dev/null +++ b/5/allcode/key.c @@ -0,0 +1,540 @@ +#include // 包含libgpiod库的头文件 +#include // 包含标准输入输出库 +#include // 包含UNIX标准库,用于usleep函数 +#include +#include +#include "record.h" +#include "control.h" + +#include "config.h" +#include "token.h" +#include "http.h" + + +#define GPIO_LINE 9 // 定义GPIO线的编号,这里是PF9 +#define VOLUME_UP_LINE 8 // 音量加按键连接到GPIOF线8 +#define VOLUME_DOWN_LINE 7 // 音量减按键连接到GPIOF线7 + +char* app_id = "f221d824-876c-42bc-b6c5-4f1581ea0f96"; + +// 正在录音 +void recording(snd_pcm_t *capture, char *buffer, FILE *pcm_file, snd_pcm_uframes_t period); + +// 音量设置相关变量 +long volume = 0; // 初始音量 +long volume_step = 10; // 每次调整的音量步长 + +//读取音频文件 +//file: 音频文件路径 +//size: 音频文件大小 +//return: 音频文件内容, NULL 表示失败 +char* load_audio_file(const char* file, size_t* size); + +//发送请求消息 +//token: 获取的access token +//audio: 音频文件内容 +//size: 音频文件大小 +//return: 响应消息正文, NULL 表示失败 +char* send_request(char* token, char* audio, size_t size); + +//处理服务器返回的响应消息 +char* process_response(char* response, size_t size); + +//创建会话 +char* create_conversation(char* authtoken); + +//调用对话API +char* chat(char* authtoken, char* conv_id, char* query); + +int main(void) +{ + // 按键引脚相关变量 + struct gpiod_chip *chip; // 定义指向GPIO芯片的指针 + struct gpiod_line *line; // 定义指向GPIO线的指针 + struct gpiod_line *volume_up; + struct gpiod_line *volume_down; + int value, last_value; // 定义当前值和上一次的值,用于检测状态变化 + int value_up, last_value_up; + int value_down, last_value_down; + + // 录音相关变量 + snd_pcm_uframes_t period = 999; // 周期大小 + snd_pcm_t *capture = NULL; // 声音采集设备句柄 + char *buffer = NULL; // 存放音频数据的缓冲区 + FILE *pcm_file = NULL; // 录音文件 + + // 回答内容 + char *result = NULL; + + // 打开GPIO芯片 + chip = gpiod_chip_open_by_label("GPIOF"); + if (!chip) + { + perror("打开GPIO芯片失败"); + return 1; + } + + // 获取GPIO线 + line = gpiod_chip_get_line(chip, GPIO_LINE); + if (!line) + { + perror("获取GPIO线失败"); + gpiod_chip_close(chip); + return 1; + } + + volume_up = gpiod_chip_get_line(chip, VOLUME_UP_LINE); + if (!volume_up) + { + perror("获取GPIO线失败"); + gpiod_chip_close(chip); + return 1; + } + + volume_down = gpiod_chip_get_line(chip, VOLUME_DOWN_LINE); + if (!volume_down) + { + perror("获取GPIO线失败"); + gpiod_chip_close(chip); + return 1; + } + + // 将GPIO线设置为输入模式 + if (gpiod_line_request_input(line, "key1")) + { + perror("请求将GPIO线设置为输入模式失败"); + gpiod_chip_close(chip); + return 1; + } + + if (gpiod_line_request_input(volume_up, "key3")) + { + perror("请求将GPIO线设置为输入模式失败"); + gpiod_chip_close(chip); + return 1; + } + + if (gpiod_line_request_input(volume_down, "key2")) + { + perror("请求将GPIO线设置为输入模式失败"); + gpiod_chip_close(chip); + return 1; + } + + // 获取初始的GPIO线值 + last_value = gpiod_line_get_value(line); + last_value_up = gpiod_line_get_value(volume_up); + last_value_down = gpiod_line_get_value(volume_down); + + // 无限循环检测GPIO线值的变化 + while (1) + { + // 获取当前的GPIO线值 + value = gpiod_line_get_value(line); + value_up = gpiod_line_get_value(volume_up); + value_down = gpiod_line_get_value(volume_down); + + // 如果当前值与上一次的值不同,说明按键状态发生了变化 + if (value != last_value) + { + // 如果当前值为0,表示按键被按下 + if (value == 0) + { + printf("key1 pressed\n"); + capture = record_start("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_stop(capture); + continue; + } + + pcm_file = fopen("stt.pcm", "wb"); + if (!pcm_file) + { + perror("Error opening output file"); + free(buffer); // 释放缓冲区 + record_stop(capture); // 关闭PCM设备 + continue; + } + } + // 如果当前值为1,表示按键被释放 + else + { + printf("key1 released\n"); + record_stop(capture); + capture = NULL; + + // 将语音转换成文本 + //读取配置信息,API KEY 和 SECRET KEY + cJSON *config = read_config("config.json"); + if (!config) { + printf("config: %s\n", cJSON_Print(config)); + cJSON_Delete(config); + } + 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); + } + printf("%s\n", authtoken->valuestring); + + //获取token + char* token = get_access_token(api_key->valuestring, secret_key->valuestring); + if (!token) { + fprintf(stderr, "获取 token 失败\n"); + cJSON_Delete(config); + } + + //读取音频文件 + size_t size; + char* buffer = load_audio_file("stt.pcm", &size); + if (!buffer) { + cJSON_Delete(config); + } + + //调用百度语音识别 API + char* response = send_request(token, buffer, size); + if (!response) { + fprintf(stderr, "调用百度语音识别 API 失败\n"); + free(buffer); + cJSON_Delete(config); + } + + //处理服务器返回的响应消息 + result = process_response(response, size); + + printf("result: %s\n", result); + + //创建会话 + char* conv_id = create_conversation(authtoken->valuestring); + + //printf("conv_id: %s\n", conv_id); + + //调用百度云千帆AppBuilder API进行对话 + char* answer = chat(authtoken->valuestring, conv_id, result); + if (!answer) + { + continue; + } + printf("< %s\n", answer); + + //释放配置信息占用的内存 + cJSON_Delete(config); + + } + + // 更新上一次的值为当前值 + last_value = value; + } + + if (value_up != last_value_up) + { + if (value_up == 0) + { + printf("key3 pressed\n"); + volume = get_playback_volume("hw:0", "Analog"); + set_playback_volume("hw:0", "Analog", volume + volume_step); + printf("当前音量:%ld\n", volume + volume_step); + } + else + { + printf("key3 released\n"); + + } + + last_value_up = value_up; + } + + if (value_down != last_value_down) + { + if (value_down == 0) + { + printf("key2 pressed\n"); + volume = get_playback_volume("hw:0", "Analog"); + set_playback_volume("hw:0", "Analog", volume - volume_step); + printf("当前音量:%ld\n", volume - volume_step); + } + else + { + printf("key2 released\n"); + + } + + last_value_down = value_down; + } + + if (value == 0 && capture) + { + // 按键按下且音频设备打开成功,进行录音 + recording(capture, buffer, pcm_file, period); + } + // 延时100毫秒,防止检测过于频繁 + //usleep(100000); + } + + // 关闭GPIO芯片 + gpiod_chip_close(chip); + return 0; +} + +// 正在录音 +void recording(snd_pcm_t *capture, char *buffer, FILE *pcm_file, snd_pcm_uframes_t period) +{ + 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); // 将读取的数据写入文件 +} + +//读取音频文件 +//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&dev_pid=1537", token); + + struct curl_slist* headers = NULL; + headers = curl_slist_append(headers, "Content-Type: audio/pcm; rate=16000"); + + char* response = 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; + } + + cJSON *content = cJSON_GetArrayItem(result, 0); + if (!content || !cJSON_IsString(content)) { + fprintf(stderr, "result 字段格式错误: 第一个元素不是字符串\n"); + cJSON_Delete(json); + return NULL; + } + + // if (cJSON_GetArraySize(result) > 0) { + // // 获取第一个元素的 "content" 字段 + // cJSON *content = cJSON_GetArrayItem(result, 0); + // //打印结果 + // printf("result: %s\n", content->valuestring); + // } + + // 为返回值分配内存 + char *result_str = (char *)malloc(content->valuestring ? strlen(content->valuestring) + 1 : 1); + if (!result_str) { + fprintf(stderr, "内存分配失败\n"); + cJSON_Delete(json); + return NULL; + } + + // 复制字符串 + strcpy(result_str, content->valuestring); + + // 清理 cJSON 对象 + cJSON_Delete(json); + + // 返回 result 字段中的第一个元素的内容 + return result_str; +} + +//创建会话 +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 = 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 = 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; +} \ No newline at end of file diff --git a/5/allcode/main.c b/5/allcode/main.c new file mode 100644 index 0000000000000000000000000000000000000000..a6b4214bbeca42779cc85da7d8272e87acc09f2f --- /dev/null +++ b/5/allcode/main.c @@ -0,0 +1,413 @@ +#include // 包含libgpiod库的头文件 +#include // 包含标准输入输出库 +#include +#include +#include +#include +#include "snowboy/snowboy-detect-c-wrapper.h" // 唤醒词检测的头文件(在工程目录下) +#include "record.h" +#include "control.h" +#include "stt.h" +#include "tts.h" +#include "config.h" +#include "token.h" +#include "http.h" +#include "chat.h" + +#define KEY1_LINE 9 // 定义GPIO线的编号,这里是PF9 +#define KEY2_LINE 7 +#define KEY3_LINE 8IEC958_AES1_CON_DCC +// 标志变量 +bool is_recording = false; // 记录是否正在录音会议内容(key1) +bool is_chatting = false; // 记录是否正在与大模型对话(key2) +bool is_talking = false; // 记录用户是否正在说话 +bool locked = false; // 当执行某个按钮的功能时,关锁,禁止其它按钮的功能 +bool has_launched = false; // 记录是否启动 +// 按键引脚相关变量 +struct gpiod_chip *chip; // 定义指向GPIO芯片的指针d +struct gpiod_line *key1; // 定义指向GPIO线的指针 +struct gpiod_line *key2; +struct gpiod_line *key3; +int key1_v, last_key1_v; // 定义当前值和上一次的值,用于检测状态变化 +int key3_v, last_key3_v; +int key2_v, last_key2_v; +// 录音相关变量 +snd_pcm_uframes_t period = 999; // 周期大小 +snd_pcm_t *capture = NULL; // 声音采集设备句柄 +char *buffer = NULL; // 存放音频数据的缓冲区 +FILE *pcm_file = NULL; // 录音文件 +// 热词检测变量 +SnowboyDetect *detector = NULL; +int status = 0; +// 内存文件 +FILE *memstream = NULL; +char *audio = NULL; +size_t audio_size = 0; +int silence = 0; // 检测到连续的静音次数 +char *meeting_contents = NULL; // 会议内容 +// 音量设置相关变量 +long volume = 0; // 初始音量 +long volume_step = 10; // 每次调整的音量步长 + +int main(void) +{ + printf("<<< 欢迎使用智能会议助手,我是小智,有什么能帮到您的?\n"); + text_to_speech("欢迎使用智能会议助手,我是小智,有什么能帮到您的?"); + + // 打开GPIO芯片 + chip = gpiod_chip_open_by_label("GPIOF"); + if (!chip) + { + perror("打开GPIO芯片失败"); + return 1; + } + + // 获取GPIO线 + key1 = gpiod_chip_get_line(chip, KEY1_LINE); + if (!key1) + { + perror("获取GPIO线失败"); + gpiod_chip_close(chip); + return 1; + } + key2 = gpiod_chip_get_line(chip, KEY2_LINE); + if (!key2) + { + perror("获取GPIO线失败"); + gpiod_chip_close(chip); + return 1; + } + key3 = gpiod_chip_get_line(chip, KEY3_LINE); + if (!key3) + { + perror("获取GPIO线失败"); + gpiod_chip_close(chip); + return 1; + } + + // 将GPIO线设置为输入模式 + if (gpiod_line_request_input(key1, "key1")) + { + perror("请求将GPIO线设置为输入模式失败"); + gpiod_chip_close(chip); + return 1; + } + if (gpiod_line_request_input(key2, "key2")) + { + perror("请求将GPIO线设置为输入模式失败"); + gpiod_chip_close(chip); + return 1; + } + if (gpiod_line_request_input(key3, "key3")) + { + perror("请求将GPIO线设置为输入模式失败"); + gpiod_chip_close(chip); + return 1; + } + + // 获取初始的GPIO线值 + last_key1_v = gpiod_line_get_value(key1); + last_key2_v = gpiod_line_get_value(key2); + last_key3_v = gpiod_line_get_value(key3); + + // 无限循环检测GPIO线值的变化 + while (1) + { + // 获取当前的GPIO线值 + key1_v = gpiod_line_get_value(key1); + key2_v = gpiod_line_get_value(key2); + key3_v = gpiod_line_get_value(key3); + + // 如果当前值与上一次的值不同,说明按键状态发生了变化 + if (key1_v != last_key1_v) + { + // 如果当前值为0,表示按键被按下 + if (!locked && key1_v == 0 && !is_recording) + { + locked = true; // 关锁,禁止其他按键响应 + // key1按下且未开始录音,开始录音 + capture = record_start("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_stop(capture); + continue; + } + + pcm_file = fopen("meeting.pcm", "wb"); + if (!pcm_file) + { + perror("Error opening output file"); + free(buffer); + record_stop(capture); + continue; + } + + is_recording = true; + printf("<<< 开始记录会议内容:\n"); + } + else if (key1_v == 0 && is_recording) + { + // key1按下且正在录音,结束录音 + record_stop(capture); + capture = NULL; + free(buffer); + buffer = NULL; + is_recording = false; + printf("<<< 会议内容记录完毕!\n"); + + // 读取音频文件 + size_t size; + buffer = load_audio_file("meeting.pcm", &size); + if (!buffer) + { + printf("<<< 读取音频文件失败\n"); + } + + // 录音转文本 + meeting_contents = speech_to_text(buffer, size); + + // 判断文本合法性 + if (0 == strcmp(meeting_contents, "")) + { + printf("<<< 会议内容为空,生成总结失败!\n"); + free(buffer); + buffer = NULL; + locked = false; + continue; + } + printf("<<< 会议内容如下: \n>>> %s\n", meeting_contents); + + // 发送给大模型作总结 + char *answer = chat(meeting_contents); + strcat(meeting_contents, " 为这次会议内容生成总结"); + // printf("%s\n", meeting_contents); + if (answer) + { + printf("<<< 总结内容如下:\n<<< %s\n", answer); + } + else + { + printf("<<< 生成总结失败!\n"); + } + + free(buffer); + buffer = NULL; + + locked = false; // 开锁,允许其他按键响应 + } + + last_key1_v = key1_v; + } + + if (key2_v != last_key2_v) + { + if (!locked && key2_v == 0 && !is_chatting) + { + locked = true; + + // 创建snowboy检测器 + detector = SnowboyDetectConstructor("common.res", "meeting.pmdl"); + if (!detector) + { + continue; + } + + capture = record_start("hw:0,1", SND_PCM_FORMAT_S16_LE, 1, 16000, &period); + if (!capture) + { + SnowboyDetectDestructor(detector); + continue; + } + + buffer = malloc(snd_pcm_frames_to_bytes(capture, period)); // 分配缓冲区 + if (!buffer) + { + perror("malloc"); + record_stop(capture); + SnowboyDetectDestructor(detector); + continue; + } + + is_chatting = true; + printf("<<< 您好,我叫小智,很高兴为您服务!\n"); + } + else if (key2_v == 0 && is_chatting) + { + SnowboyDetectDestructor(detector); + record_stop(capture); + capture = NULL; + is_chatting = false; + printf("<<< 感谢您的使用,再见!\n"); + locked = false; + } + + last_key2_v = key2_v; + } + + if (key3_v != last_key3_v) + { + if (!locked && key3_v == 0) + { + locked = true; + + // 发送开会提醒邮件 + const char *url = "http://192.168.43.245:5000/send_email"; + const char *json_data = "{\"content\":\"

七月七日上午10:00在教8楼答辩

\"}"; + int result = send_email(url, json_data); + if (result == 0) + { + printf("<<< 邮件发送成功!\n"); + } + else + { + printf("<<< 邮件发送失败!\n"); + } + + locked = false; + } + last_key3_v = key3_v; + } + + if (is_recording && 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, pcm_file); // 将读取的数据写入文件 + } + + if (is_chatting && detector) + { + snd_pcm_sframes_t frames = snd_pcm_readi(capture, buffer, period); + if (frames < 0) + { + fprintf(stderr, "Error from read: %s\n", snd_strerror(frames)); + snd_pcm_recover(capture, frames, 0); + continue; + } + status = SnowboyDetectRunDetection(detector, (int16_t *)buffer, snd_pcm_frames_to_bytes(capture, frames) / sizeof(int16_t), 0); + // 检测到说话声,开始录音 + if (status >= 0) + { + if (!memstream) + { + // 打开内存文件 + memstream = open_memstream(&audio, &audio_size); + if (!memstream) + { + perror("open_memstream"); + continue; + } + is_talking = true; + } + } + // 检测到持续2秒的静音,结束录音 + if (is_talking) + { + if (status == -2) + { + silence++; + } + if (status == 0) + { + silence = 0; + } + if (silence > 32) + { + is_talking = false; + printf("<<< 正在为您解答,请稍后!\n"); + 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", text); + } + + // 语音控制结束 + if (0 == strcmp(text, "再见。")) + { + SnowboyDetectDestructor(detector); + record_stop(capture); + capture = NULL; + is_chatting = false; + printf("<<< 感谢您的使用,再见!\n"); + text_to_speech("感谢您的使用,再见!"); + locked = false; + continue; + } + + // 音量控制 + if (0 == strcmp(text, "加大音量。")) + { + printf("<<< 好的,为您提高音量: \n"); + volume = get_playback_volume("hw:0", "Analog"); + set_playback_volume("hw:0", "Analog", volume + volume_step); + printf("<<< 当前音量:%ld\n", volume + volume_step); + } + else if (0 == strcmp(text, "减小音量。")) + { + printf("<<< 好的,为您降低音量: \n"); + volume = get_playback_volume("hw:0", "Analog"); + set_playback_volume("hw:0", "Analog", volume - volume_step); + printf("<<< 当前音量:%ld\n", volume - volume_step); + } + else + { + // 将文本发送给大模型 + char *answer = chat(text); + if (answer) + { + printf("<<< %s\n", answer); + } + else + { + printf("<<< 抱歉,我没听清楚,请重新提问。\n"); + answer = "抱歉,我没听清楚,请重新提问。"; + } + + // 回答内容转语音 + text_to_speech(answer); + } + + // 恢复录音 + snd_pcm_prepare(capture); + printf("<<< 您可以继续提问:\n"); + } + } + + if (memstream) + { + fwrite(buffer, 1, snd_pcm_frames_to_bytes(capture, frames), memstream); + } + } + } + } + + // 关闭GPIO芯片 + gpiod_chip_close(chip); + return 0; +} \ No newline at end of file diff --git a/5/allcode/play.c b/5/allcode/play.c new file mode 100644 index 0000000000000000000000000000000000000000..98d3af3428eef75c37e8f5e3a92a7ac4bbbe1505 --- /dev/null +++ b/5/allcode/play.c @@ -0,0 +1,142 @@ +#include +#include +#include +#include // 用于处理错误码 + +// 开始播放 +snd_pcm_t *play_start(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(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_stop(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; // 每个周期的帧数 + char *buffer; // 缓冲区,用于存储采集到的音频数据 + FILE *pcm_file; // 输出PCM文件 + int err; // 用于存储错误码 + + playback = play_start("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_stop(playback); + return 1; + } + + // 打开输出文件 + pcm_file = fopen("output.pcm", "rb"); + if (!pcm_file) + { + perror("Error opening output file"); + free(buffer); // 释放缓冲区 + play_stop(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); // 先读出数据 + + snd_pcm_sframes_t frames = snd_pcm_writei(playback, buffer, snd_pcm_bytes_to_frames(playback, bytes)); // 数据写到PCM设备// + if (frames < 0) + { + fprintf(stderr, "Error from read: %s\n", snd_strerror(frames)); + if (frames == -EPIPE) + { + snd_pcm_prepare(playback); + } + } + } + + // 清理资源 + free(buffer); // 释放缓冲区 + fclose(pcm_file); // 关闭文件 + play_stop(playback); + + printf("Playing complete.\n"); + + return 0; +} +#endif \ No newline at end of file diff --git a/5/allcode/play.h b/5/allcode/play.h new file mode 100644 index 0000000000000000000000000000000000000000..1acfa9656a858363e322a3df8562cfb752d10fed --- /dev/null +++ b/5/allcode/play.h @@ -0,0 +1,14 @@ +#ifndef PLAY_H +#define PLAY_H + +#include + +snd_pcm_t* play_start(const char* name, + snd_pcm_format_t format, + unsigned int channel, + unsigned int rate, + snd_pcm_uframes_t* period); +//停止播放 +void play_stop(snd_pcm_t* playback); + +#endif diff --git a/5/allcode/record.c b/5/allcode/record.c new file mode 100644 index 0000000000000000000000000000000000000000..8c96f899eb9dc2796cd40d9e49eb604a1d6983dd --- /dev/null +++ b/5/allcode/record.c @@ -0,0 +1,204 @@ +#include +#include +#include // 用于处理错误码 +#include "record.h" + +// 获取设备 +snd_pcm_t *record_start(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; + } + //printf("设置的周期大小为:%d\n", *period); + + // 设置硬件参数 + 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); + //printf("period: %d frames\n", *period); + + return capture; +} + +// 关闭设备 +void record_stop(snd_pcm_t *capture) +{ + snd_pcm_drain(capture); // 排空PCM设备 + snd_pcm_close(capture); // 关闭PCM设备 +} + +// // 开始录音 +// snd_pcm_t *start_recording(char **buffer, FILE *pcm_file, snd_pcm_uframes_t *period) +// { +// snd_pcm_t *capture; +// capture = record_start("hw:0,1", SND_PCM_FORMAT_S16_LE, 2, 44100, period); +// if (!capture) +// { +// fprintf(stderr, "Failed to start recording\n"); +// return NULL; +// } + +// *buffer = (char *)malloc(snd_pcm_frames_to_bytes(capture, *period)); +// if (!*buffer) +// { +// perror("malloc"); +// record_stop(capture); +// capture = NULL; +// return NULL; +// } + +// pcm_file = fopen("output.pcm", "wb"); +// if (!pcm_file) +// { +// perror("Error opening output file"); +// free(*buffer); +// record_stop(capture); +// capture = NULL; +// return NULL; +// } + +// printf("Recording started\n"); +// return capture; +// } + +// // 停止录音 +// void stop_recording(snd_pcm_t *capture, char *buffer, FILE *pcm_file) +// { +// if (capture) +// { +// free(buffer); +// buffer = NULL; + +// fclose(pcm_file); +// pcm_file = NULL; + +// record_stop(capture); +// capture = NULL; + +// printf("Recording stopped\n"); +// } +// } + + +#if 0 // 0改成1就可参与编译 +int main() +{ + snd_pcm_t *capture; // PCM设备句柄 + snd_pcm_uframes_t period; // 每个周期的帧数 + char *buffer; // 缓冲区,用于存储采集到的音频数据 + FILE *pcm_file; // 输出PCM文件 + int err; // 用于存储错误码 + + capture = record_start("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_stop(capture); + return 1; + } + + // 打开输出文件 + pcm_file = fopen("output.pcm", "wb"); + if (!pcm_file) + { + perror("Error opening output file"); + free(buffer); // 释放缓冲区 + record_stop(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)); + if (frames == -EPIPE) + { + snd_pcm_prepare(capture); + } + } + + fwrite(buffer, snd_pcm_frames_to_bytes(capture, frames), 1, pcm_file); // 将读取的数据写入文件 + } + + // 清理资源 + free(buffer); // 释放缓冲区 + fclose(pcm_file); // 关闭文件 + record_stop(capture); + + printf("Recording complete.\n"); + + return 0; +} +#endif \ No newline at end of file diff --git a/5/allcode/record.h b/5/allcode/record.h new file mode 100644 index 0000000000000000000000000000000000000000..7c474ed7eb82e5e38bb54aa98f70bc3a31a49b58 --- /dev/null +++ b/5/allcode/record.h @@ -0,0 +1,16 @@ +#ifndef RECORD_H +#define RECORD_H + +#include + +// 开始录音 +snd_pcm_t *record_start(const char *name, + snd_pcm_format_t format, + unsigned int channel, + unsigned int rate, + snd_pcm_uframes_t *period); + +// 结束录音 +void record_stop(snd_pcm_t *capture); + +#endif \ No newline at end of file diff --git a/5/allcode/stt.c b/5/allcode/stt.c new file mode 100644 index 0000000000000000000000000000000000000000..33e6d00c8285522ba57c0eb0ed848035a8d369b3 --- /dev/null +++ b/5/allcode/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 = 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/5/allcode/stt.h b/5/allcode/stt.h new file mode 100644 index 0000000000000000000000000000000000000000..04566414ad8823e26c7229094df6bb381c8450b1 --- /dev/null +++ b/5/allcode/stt.h @@ -0,0 +1,10 @@ +#ifndef STT_H +#define STT_H + +#include + +char* load_audio_file(const char* file, size_t* size); + +char* speech_to_text(char* audio, size_t size); + +#endif \ No newline at end of file diff --git a/5/allcode/test.c b/5/allcode/test.c new file mode 100644 index 0000000000000000000000000000000000000000..cb6336bfcdaeb593a38d7f2fbae8f9a09f76916f --- /dev/null +++ b/5/allcode/test.c @@ -0,0 +1,64 @@ + +#include +#include +#include +#include + +int send_post_request(const char *url, const char *json_data) { + CURL *curl; + CURLcode res; + int result = 0; + + // Initialize CURL + curl_global_init(CURL_GLOBAL_DEFAULT); + curl = curl_easy_init(); + + if(curl) { + // Set the URL for the POST request + curl_easy_setopt(curl, CURLOPT_URL, url); + + // Set the POST fields + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data); + + // Set the headers for the POST request + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Content-Type: application/json"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + // Perform the request, res will get the return code + res = curl_easy_perform(curl); + + // Check for errors + if(res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); + result = 1; + } + + // Cleanup + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + } else { + fprintf(stderr, "Failed to initialize CURL\n"); + result = 1; + } + + // Cleanup CURL globally + curl_global_cleanup(); + + return result; +} + +int main(void) { + const char *url = "http://192.168.43.245:5000/send_email"; + const char *json_data = "{\"content\":\"

Meeting Reminder

Don't forget the meeting tomorrow at 10 AM.

\"}"; + + int result = send_post_request(url, json_data); + + if(result == 0) { + printf("POST request sent successfully\n"); + } else { + printf("Failed to send POST request\n"); + } + + return result; +} diff --git a/5/allcode/token.c b/5/allcode/token.c new file mode 100644 index 0000000000000000000000000000000000000000..43301c0c0e28f75778b67f9f3fcac72d8ce2a7c3 --- /dev/null +++ b/5/allcode/token.c @@ -0,0 +1,96 @@ +#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 = 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; +} + +#if 0 +int main() +{ + struct cJSON* config = read_config("app.json"); + if (!config) + { + return EXIT_FAILURE; + } + + struct cJSON* api_key = cJSON_GetObjectItem(config, "api_key"); + struct cJSON* secret_key = cJSON_GetObjectItem(config, "secret_key"); + if (!api_key || !secret_key) + { + fprintf(stderr, "api_key or secret_key not found in config file\n"); + cJSON_Delete(config); + return EXIT_FAILURE; + } + + char *token = get_access_token(api_key->valuestring, secret_key->valuestring); + if (token) { + printf("Access Token: %s\n", token); + free(token); // 释放内存 + } + + cJSON_Delete(config); + + return 0; +} +# endif \ No newline at end of file diff --git a/5/allcode/token.h b/5/allcode/token.h new file mode 100644 index 0000000000000000000000000000000000000000..28ec2b6bbbc234b5ed264a667244574203af6cd5 --- /dev/null +++ b/5/allcode/token.h @@ -0,0 +1,6 @@ +#ifndef TOKEN_H +#define TOKEN_H + +char *get_access_token(const char *ak, const char *sk); + +#endif \ No newline at end of file diff --git a/5/allcode/tts.c b/5/allcode/tts.c new file mode 100644 index 0000000000000000000000000000000000000000..6130e1a097ebc02737e3393fe7741110ba74b5e3 --- /dev/null +++ b/5/allcode/tts.c @@ -0,0 +1,269 @@ +#include +#include +#include +#include +#include +#include "config.h" +#include "http.h" +#include "play.h" + +char *appid = "1697188198"; + +// 生成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 + 1) * 3; + *decoded = (char *)malloc(decoded_size); + if (!*decoded) + { + return 0; + } + + // 解码base64字符串 + int size = b64_pton(base64, *decoded, decoded_size); + if (size < 0) + { + return 0; + } + + return size; +} + +// 发送请求 +// text: 需要转为语音的文本 +// 返回值: API返回的响应消息,失败返回 NULL +static char *tts_send_request(char *text) +{ + // 从配置文件中获取 API 密钥 + cJSON *config = read_config("config.json"); + if (!config) + { + return NULL; + } + + cJSON *ttstoken = cJSON_GetObjectItem(config, "ttstoken"); + if (!ttstoken) + { + fprintf(stderr, "无法获取ttstoken\n"); + return NULL; + } + + // printf("%s\n", ttstoken->valuestring); + + 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"); // 灿灿2.0 + cJSON_AddNumberToObject(audio, "rate", 16000); + cJSON_AddStringToObject(audio, "speed_ratio", "1.2"); + 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); + //puts(json); + + return post(url, headers, json, &size); +} + +// 处理服务器返回的响应消息 +// response: API返回的响应消息 +// size: 音频数据的大小 +// 返回值:处理后的语音数据 +static char *tts_process_response(char *response, size_t *size) +{ + // 解析 JSON 响应 + 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) + { + printf("<<< 回复内容超出限制,无法转换为语音!\n"); + 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; + } + + // 对data字段的值进行base64解码 + 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) +{ + // 调用语音合成 API + char *response = tts_send_request(text); + if (!response) + { + fprintf(stderr, "调用语音合成 API 失败\n"); + return; + } + + // printf("返回的响应:\n"); + // puts(response); + + // 处理服务器返回的响应消息 + size_t size = 0; + char *audio = tts_process_response(response, &size); + if (!audio) + { + return; + } + + // 播放音频 + snd_pcm_t *playback; // PCM设备句柄 + snd_pcm_uframes_t period = 1024; // 每个周期的帧数 + char *buffer; // 缓冲区,用于存储从文件中读取的音频数据 + FILE *pcm_file; // 输出PCM文件 + int err; // 用于存储错误码 + // 打开音频数据 + pcm_file = fmemopen(audio, size, "rb"); + if (!pcm_file) + { + perror("Error opening output file"); + return; + } + + playback = play_start("hw:0,0", SND_PCM_FORMAT_S16_LE, 1, 16000, &period); + if (!playback) + { + fclose(pcm_file); + return; + } + + //printf("period in tts.c: %d frames\n", period); + + buffer = (char *)malloc(snd_pcm_frames_to_bytes(playback, period)); // 分配缓冲区 + if (!buffer) + { + printf("buffer error\n"); + perror("malloc"); + play_stop(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_stop(playback); + + free(response); +} + +#if 0 +int main() +{ + // 从标准输入读取文本 + char line[1024]; + while (fgets(line, sizeof(line), stdin)) + { + // 去除换行符 + line[strcspn(line, "\n")] = '\0'; + + // 调用语音合成 API + text_to_speech(line); + } + return 0; +} +#endif \ No newline at end of file diff --git a/5/allcode/tts.h b/5/allcode/tts.h new file mode 100644 index 0000000000000000000000000000000000000000..118a86e950490395faaf4c8f318a764268763d71 --- /dev/null +++ b/5/allcode/tts.h @@ -0,0 +1,16 @@ +#ifndef TTS_H +#define TTS_H + +#include + +char *gen_uuid(void); + +size_t base64_decode(const char *base64, char **decoded); + +static char *tts_send_request(char *text); + +static char *tts_process_response(char *response, size_t *size); + +char* text_to_speech(char *text); + +#endif \ No newline at end of file diff --git a/5/allcode/tts.txt b/5/allcode/tts.txt new file mode 100644 index 0000000000000000000000000000000000000000..9879ad7559ea460a53fd0ad399f2a293c488a333 --- /dev/null +++ b/5/allcode/tts.txt @@ -0,0 +1,11 @@ +"尊敬的同事们,感谢大家今天能够参加这次会议。在过去的一年里,我们公司在市场上取得了显著的成绩,但我们也面临着不少挑战。今天,我想和大家分享一下我们对2024年市场战略的一些思考和规划。 + +首先,我们将继续加强我们的产品线,特别是在新兴的智能科技领域。我们计划推出几款新的智能产品,以满足市场的需求。这些产品将集成最新的人工智能技术,提供更加个性化和高效的用户体验。 + +其次,我们将加大市场推广的力度。我们计划通过多渠道营销策略,包括社交媒体、在线广告和合作伙伴关系,来提高我们品牌的知名度和市场占有率。 + +此外,我们还将重点关注客户服务。优质的客户服务是建立客户忠诚度的关键。我们将优化客户支持流程,确保我们的客户能够得到快速和专业的服务。 + +最后,我想强调的是团队合作的重要性。一个成功的市场战略需要我们所有人的共同努力。我希望我们每个人都能够积极参与到这个过程中,为实现我们的目标贡献自己的力量。 + +在接下来的时间里,我将详细阐述我们的战略规划,并邀请大家提出宝贵的意见和建议。让我们共同努力,迎接2024年的挑战和机遇。谢谢大家。" \ No newline at end of file diff --git a/5/allcode/utils.c b/5/allcode/utils.c new file mode 100644 index 0000000000000000000000000000000000000000..61e11975297e769059bffe2d6b99da0e0e0a9e36 --- /dev/null +++ b/5/allcode/utils.c @@ -0,0 +1,41 @@ +#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 + 1) * 3; + // 解码base64字符串 + 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("6YGT5Y+v6YGT6Z2e5bi46YGT", text); + printf("%*s\n", size, text); +} diff --git a/5/allcode/wake.c b/5/allcode/wake.c new file mode 100644 index 0000000000000000000000000000000000000000..22a9a326e1431c240710a1036455cdc0df2dc7e3 --- /dev/null +++ b/5/allcode/wake.c @@ -0,0 +1,131 @@ +#include "snowboy/snowboy-detect-c-wrapper.h" +#include +#include +#include "record.h" +#include "stt.h" + +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_start("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_stop(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"); + recording = 1; + //打开内存文件 + memstream = open_memstream(&audio, &audio_size); + if (!memstream) + { + perror("open_memstream"); + continue; + } + } + + if (recording) + { + if (status == -2) + { + silence++; + } + + if (status == 0) + { + silence = 0; + } + + if (silence > 32) + { + printf("停止录音\n"); + 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) + { + puts(text); + } + // 恢复录音 + snd_pcm_prepare(capture); + } + } + + if (memstream) + { + fwrite(buffer, 1, snd_pcm_frames_to_bytes(capture, frames), memstream); + } + } + } + + + + free(buffer); + record_stop(capture); + SnowboyDetectDestructor(detector); + + return EXIT_SUCCESS; +}