From 09fd447847f280c4236ec7026197002609793425 Mon Sep 17 00:00:00 2001 From: LaFengQinCai <2964773991@qq.com> Date: Wed, 10 Jul 2024 14:58:33 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E7=BB=84=E6=99=BA=E8=83=BD?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=E5=AD=A6=E4=B9=A0=E5=8A=A9=E6=89=8B=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 1/CMakeLists.txt | 40 +-- 1/http.c | 110 ++++++++ 1/http.h | 9 + 1/key2.c | 190 ------------- 1/language.c | 681 +++++++++++++++++++++++++++++++++++++++++++++++ 1/play.c | 21 +- 1/play.h | 14 + 1/record.c | 132 +++++++++ 1/record.h | 16 ++ 1/stt.c | 157 +++++++++++ 1/stt.h | 8 + 1/wake.c | 112 -------- 1/wakechat.c | 269 ------------------- 13 files changed, 1158 insertions(+), 601 deletions(-) create mode 100644 1/http.c create mode 100644 1/http.h delete mode 100644 1/key2.c create mode 100644 1/language.c create mode 100644 1/play.h create mode 100644 1/record.c create mode 100644 1/record.h create mode 100644 1/stt.c create mode 100644 1/stt.h delete mode 100644 1/wake.c delete mode 100644 1/wakechat.c diff --git a/1/CMakeLists.txt b/1/CMakeLists.txt index 71b483c..adce337 100644 --- a/1/CMakeLists.txt +++ b/1/CMakeLists.txt @@ -1,20 +1,24 @@ cmake_minimum_required(VERSION 3.0.0) -project(intelligent-language-learning VERSION 0.1.0 LANGUAGES C) +project(intelligence-language-learning VERSION 0.1.0 LANGUAGES C) add_executable(intelligent-language-learning main.c) add_executable(control control.c) target_link_libraries(control asound) -# add_executable(record record.c) -# target_link_libraries(record asound) +#add_executable(record record.c) +#target_link_libraries(record asound) -add_executable(play play.c) -target_link_libraries(play asound) +#add_executable(play play.c) +#target_link_libraries(play asound) add_executable(key key.c record.c) target_link_libraries(key gpiod asound) +# add_executable(key2 key2.c token.c http.c config.c record.c) +# target_compile_definitions(key2 PRIVATE _GNU_SOURCE) +# target_link_libraries(key2 curl cjson gpiod asound) + add_subdirectory(snowboy) add_executable(wake wake.c record.c stt.c token.c http.c config.c) target_link_libraries(wake snowboy-wrapper snowboy-detect cblas m stdc++ asound curl cjson) @@ -23,23 +27,21 @@ target_compile_definitions(wake PRIVATE _GNU_SOURCE) add_executable(jrsc jrsc.c) target_link_libraries(jrsc curl cjson) -# add_executable(token token.c http.c config.c) -# target_compile_definitions(token PRIVATE _GNU_SOURCE) -# target_link_libraries(token curl cjson) - -# add_executable(stt stt.c token.c http.c config.c) -# target_compile_definitions(stt PRIVATE _GNU_SOURCE) -# target_link_libraries(stt curl cjson) - - -add_executable(key2 key2.c token.c http.c config.c record.c) -target_compile_definitions(key2 PRIVATE _GNU_SOURCE) -target_link_libraries(key2 curl cjson gpiod asound) - add_executable(chat chat.c config.c http.c) target_link_libraries(chat cjson curl) target_compile_definitions(chat PRIVATE _GNU_SOURCE) add_executable(wakechat wakechat.c config.c http.c record.c stt.c token.c ) target_link_libraries(wakechat cjson curl snowboy-wrapper snowboy-detect cblas m stdc++ asound) -target_compile_definitions(wakechat PRIVATE _GNU_SOURCE) \ No newline at end of file +target_compile_definitions(wakechat 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) + +add_executable(language language.c config.c http.c play.c record.c stt.c token.c) +target_link_libraries(language cjson curl uuid resolv snowboy-wrapper snowboy-detect cblas m stdc++ asound) +target_compile_definitions(language PRIVATE _GNU_SOURCE) \ No newline at end of file diff --git a/1/http.c b/1/http.c new file mode 100644 index 0000000..dbceee8 --- /dev/null +++ b/1/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/1/http.h b/1/http.h new file mode 100644 index 0000000..16f70b7 --- /dev/null +++ b/1/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 \ No newline at end of file diff --git a/1/key2.c b/1/key2.c deleted file mode 100644 index 5f8bb39..0000000 --- a/1/key2.c +++ /dev/null @@ -1,190 +0,0 @@ -#include -#include -#include -#include -#include "record.h" -#include "config.h" -#include "token.h" -#include "http.h" -#include - -#define GPIO_LINE 9 // 定义GPIO线的编号,这里是PF9 - -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; -} - -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; -} - -void process_response(char* response, size_t size) { - cJSON *json = cJSON_ParseWithLength(response, size); - if (!json) { - fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); - return; - } - cJSON* err_no = cJSON_GetObjectItem(json, "err_no"); - if (!err_no) { - fprintf(stderr, "err_no 字段不存在\n"); - cJSON_Delete(json); - return; - } - 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; - } - cJSON *result = cJSON_GetObjectItem(json, "result"); - if (!result) { - fprintf(stderr, "JSON 格式错误: 找不到'result' 字段\n"); - cJSON_Delete(json); - return; - } - if (cJSON_GetArraySize(result) > 0) { - cJSON *content = cJSON_GetArrayItem(result, 0); - printf("result: %s\n", content->valuestring); - } - cJSON_Delete(json); -} - -int main(void) { - struct gpiod_chip *chip; - struct gpiod_line *line; - int value, last_value; - snd_pcm_uframes_t period; - snd_pcm_t* capture = NULL; - char* buffer = NULL; - FILE* fp = NULL; - size_t size; - char* audio_buffer = NULL; - - chip = gpiod_chip_open_by_label("GPIOF"); - if (!chip) { - perror("打开GPIO芯片失败"); - return 1; - } - - line = gpiod_chip_get_line(chip, GPIO_LINE); - if (!line) { - perror("获取GPIO线失败"); - gpiod_chip_close(chip); - return 1; - } - - if (gpiod_line_request_input(line, "key1")) { - perror("请求将GPIO线设置为输入模式失败"); - gpiod_chip_close(chip); - return 1; - } - - last_value = gpiod_line_get_value(line); - - while (1) { - value = gpiod_line_get_value(line); - if (value != last_value) { - if (value == 0) { - printf("key pressed\n"); - capture = record_open("hw:0,1", SND_PCM_FORMAT_S16_LE, 1, 16000, &period); // 修改为单声道、16kHz 采样率 - 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); - continue; - } - } else { - printf("key released\n"); - record_close(capture); - capture = NULL; - fclose(fp); - free(buffer); - - audio_buffer = load_audio_file("output.pcm", &size); - if (!audio_buffer) { - return EXIT_FAILURE; - } - printf("Audio file size: %zu bytes\n", size); // 打印音频文件大小 - cJSON *config = read_config("config.json"); - if (!config) { - printf("config: %s\n", cJSON_Print(config)); - free(audio_buffer); - return EXIT_FAILURE; - } - 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); - free(audio_buffer); - return EXIT_FAILURE; - } - char* token = get_access_token(api_key->valuestring, secret_key->valuestring); - cJSON_Delete(config); - if (!token) { - fprintf(stderr, "获取 token 失败\n"); - free(audio_buffer); - return EXIT_FAILURE; - } - char* response = send_request(token, audio_buffer, size); - free(audio_buffer); - free(token); - if (!response) { - fprintf(stderr, "调用百度语音识别 API 失败\n"); - return EXIT_FAILURE; - } - process_response(response, size); - free(response); - } - last_value = value; - } - if (value == 0 && capture) { - 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); - } - fwrite(buffer, snd_pcm_frames_to_bytes(capture, frames), 1, fp); - } - } - - gpiod_line_release(line); - gpiod_chip_close(chip); - return 0; - -} \ No newline at end of file diff --git a/1/language.c b/1/language.c new file mode 100644 index 0000000..dd88cfb --- /dev/null +++ b/1/language.c @@ -0,0 +1,681 @@ +#include "snowboy/snowboy-detect-c-wrapper.h" // 引入Snowboy唤醒词检测库的C语言接口 +#include // 标准库,提供内存分配、随机数等函数 +#include // 标准输入输出库 +#include "record.h" // 音频录制相关函数 +#include "stt.h" // 语音识别相关函数 +#include "config.h" // 配置文件读取相关函数 +#include "http.h" // HTTP请求相关函数 +#include "play.h" // 音频播放相关函数 +#include // UUID生成库 +#include // DNS解析库 + +// 定义平台的app_id和appid +char *app_id = "547bf25e-47a0-4dd7-90c2-a78f36a6bc8f"; +char *appid = "5042405183"; + +// 语音识别模型选择 +char *dev_pid = "1537"; // 中文接口 + +// 语音识别接口选择标记:0表示中文,1表示英文 +int flag = 0; + +// 获取音频回放通道音量的函数 +// card: 声卡名称 +// selem: 控制项名称 +// 返回值: 当前音量 +int get_playback_volume(const char *card, const char *selem) +{ + // 声明变量 + int err; + snd_mixer_t *handle; // 混音器句柄 + snd_mixer_selem_id_t *sid; // 混音器简单元素ID + + // 打开混音器,如果失败打印错误并返回错误码 + 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 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; +} + +// 调整音量的函数 +void adjust_volume(long volume_change) +{ + // 声明变量 + long min, max; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + const char *card = "default"; // 使用默认声卡 + const char *selem_name = "PCM"; // 调整PCM音量 + + // 打开混音器并加载控制项 + snd_mixer_open(&handle, 0); + snd_mixer_attach(handle, card); + snd_mixer_selem_register(handle, NULL, NULL); + snd_mixer_load(handle); + + // 分配简单元素ID并设置索引和名称 + snd_mixer_selem_id_alloca(&sid); + snd_mixer_selem_id_set_index(sid, 0); + snd_mixer_selem_id_set_name(sid, selem_name); + snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid); + + // 获取音量范围 + snd_mixer_selem_get_playback_volume_range(elem, &min, &max); + + // 获取当前音量 + long volume; + snd_mixer_selem_get_playback_volume(elem, 0, &volume); + + // 调整音量,确保不会超出范围 + volume += volume_change; + if (volume < min) + volume = min; + if (volume > max) + volume = max; + + // 设置调整后的音量 + snd_mixer_selem_set_playback_volume_all(elem, volume); + + // 关闭混音器 + snd_mixer_close(handle); +} + +// 创建会话的函数 +// authtoken: 授权令牌 +// 返回值: 会话ID +char *create_conversation(char *authtoken) +{ + // 定义API的URL + char *url = "https://qianfan.baidubce.com/v2/app/conversation"; + + // 使用asprintf拼接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); + + // 创建JSON对象并添加字段 + cJSON *obj = cJSON_CreateObject(); + cJSON_AddStringToObject(obj, "app_id", app_id); + // 将JSON对象转换为字符串 + char *json = cJSON_Print(obj); + + // 发送POST请求,并获取响应 + 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); + + // 如果请求失败,返回NULL + if (!response) + { + return NULL; + } + + // 解析响应的JSON数据 + obj = cJSON_ParseWithLength(response, size); + free(response); + + // 如果解析失败,打印错误并返回NULL + if (!obj) + { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + + // 从响应中获取conversation_id + cJSON *conversation_id = cJSON_GetObjectItem(obj, "conversation_id"); + if (!conversation_id) + { + fprintf(stderr, "conversation_id 字段不存在\n"); + cJSON_Delete(obj); + return NULL; + } + + // 复制conversation_id并返回 + 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) +{ + // 定义API的URL + char *url = "https://qianfan.baidubce.com/v2/app/conversation/runs"; + + // 使用asprintf拼接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); + + // 创建JSON对象并添加字段 + 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对象转换为字符串 + char *json = cJSON_Print(obj); + + // 发送POST请求,并获取响应 + 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); + + // 如果请求失败,返回NULL + if (!response) + { + return NULL; + } + + // 解析响应的JSON数据 + obj = cJSON_ParseWithLength(response, size); + free(response); + + // 如果解析失败,打印错误并返回NULL + if (!obj) + { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + + // 从响应中获取answer字段 + cJSON *answer = cJSON_GetObjectItem(obj, "answer"); + if (!answer) + { + fprintf(stderr, "answer 字段不存在\n"); + cJSON_Delete(obj); + return NULL; + } + + // 复制answer字段的值并返回 + char *retval = strdup(answer->valuestring); + cJSON_Delete(obj); + + return retval; +} + +// 生成UUID的函数 +char *gen_uuid(void) +{ + // 静态数组用于存放UUID字符串 + static char uuid_str[37]; + uuid_t uuid; + // 生成UUID + uuid_generate(uuid); + // 将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; + } + + // 对base64字符串进行解码 + int size = b64_pton(base64, *decoded, decoded_size); + if (size < 0) + { + return 0; + } + + return size; +} + +// 发送请求 +static char *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; + } + + 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", "conference-assistant"); + cJSON_AddItemToObject(obj, "user", user); + + cJSON *audio = cJSON_CreateObject(); + cJSON_AddStringToObject(audio, "voice_type", "BV700_V2_streaming"); // 灿灿2.0 + 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 http_post(url, headers, json, &size); +} + +// 处理服务器返回的响应消息 +static char *process_response(char *response, size_t *size) +{ + // 解析 JSON 响应 + cJSON *obj = cJSON_Parse(response); + if (!obj) + { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + + // 检查响应码是否为3000,表示成功 + 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; + } + + // 获取data字段,即base64编码的音频数据 + 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 = send_request(text); + if (!response) + { + fprintf(stderr, "调用语音合成 API 失败\n"); + return; + } + + // puts(response); + + // 处理服务器返回的响应消息 + size_t size = 0; + char *audio = process_response(response, &size); + if (!audio) + { + return; + } + + // 播放音频 + snd_pcm_t *playback; // PCM设备句柄 + snd_pcm_uframes_t period = 999; // 每个周期的帧数 + char *buffer; // 缓冲区,用于存储从文件中读取的音频数据 + FILE *pcm_file; // 输出PCM文件 + int err; // 用于存储错误码 + + // 使用fmemopen创建一个基于内存的文件流,用于读取解码后的音频数据 + 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: %d 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); +} + +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; + } + + // 创建会话 + char *conv_id = create_conversation(authtoken->valuestring); + if (!conv_id) + { + cJSON_Delete(config); + return EXIT_FAILURE; + } + + // 创建snowboy检测器 + SnowboyDetect *detector = SnowboyDetectConstructor("common.res", "nihao.pmdl"); + if (!detector) + { + cJSON_Delete(config); + return EXIT_FAILURE; + } + + // 设置灵敏度 + SnowboyDetectSetSensitivity(detector, "0.6"); + + // 获取检测器支持的音频数据参数 + 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,1", SND_PCM_FORMAT_S16_LE, channels, rate, &period); + if (!capture) + { + SnowboyDetectDestructor(detector); + cJSON_Delete(config); + return EXIT_FAILURE; + } + + // 分配缓冲区,存储采集的音频数据 + char *buffer = malloc(snd_pcm_frames_to_bytes(capture, period)); + if (!buffer) + { + perror("malloc"); + record_close(capture); + SnowboyDetectDestructor(detector); + cJSON_Delete(config); + return EXIT_FAILURE; + } + + int recording = 0; + int silence = 0; + FILE *memstream = NULL; + char *audio = NULL; + size_t audio_size = 0; + + int detected = 0; // 添加唤醒词检测标志 + + while (1) + { + + // 从PCM读取数据 + 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; + } + + // 运行唤醒词检测 + int status = SnowboyDetectRunDetection(detector, (int16_t *)buffer, snd_pcm_frames_to_bytes(capture, frames) / sizeof(int16_t), 0); + if (status > 0 && !detected) + { // 检测到唤醒词并且之前未检测到 + printf("检测到唤醒词,开始录音\n"); + recording = 1; + detected = 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; + detected = 0; // 复位唤醒词检测标志 + silence = 0; + if (memstream) + { + fclose(memstream); + memstream = NULL; + } + + if (audio_size) + { + // 暂停录音 + snd_pcm_drop(capture); + // 识别语音 + dev_pid = (flag == 1 ? "1737" : "1537"); // 1537中文接口, 1737英文接口 + char *text = speech_to_text(audio, audio_size, dev_pid); + if (text) + { + puts(text); + // 调用百度云千帆AppBuilder API进行对话 + char *answer = chat(authtoken->valuestring, conv_id, text); + if (answer) + { + char *flagStr = strstr(answer, "flag="); // flagStr指向'flag='第一次出现的位置 + if (flagStr) + { + flag = flagStr[5] - '0'; // 将字符'0'或'1'转为数字 + *flagStr = '\0'; // 删除标记信息 + } + printf("< %s\n", answer); + // 将回答转换为语音并播放 + text_to_speech(answer); + free(answer); + } + free(text); + } + // 恢复录音 + snd_pcm_prepare(capture); + } + } + + if (memstream) + { + fwrite(buffer, 1, snd_pcm_frames_to_bytes(capture, frames), memstream); + } + } + } + + free(buffer); + record_close(capture); + SnowboyDetectDestructor(detector); + cJSON_Delete(config); + free(conv_id); + + return EXIT_SUCCESS; +} diff --git a/1/play.c b/1/play.c index c951a7e..fb33553 100644 --- a/1/play.c +++ b/1/play.c @@ -4,7 +4,7 @@ #include // 用于处理错误码 //开始播放 -snd_pcm_t* play_start(const char* name, +snd_pcm_t* play_open(const char* name, snd_pcm_format_t format, unsigned int channel, unsigned int rate, @@ -50,23 +50,19 @@ snd_pcm_t* play_start(const char* name, 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); @@ -74,12 +70,13 @@ snd_pcm_t* play_start(const char* name, } //停止播放 -void play_stop(snd_pcm_t* 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设备句柄 @@ -88,7 +85,7 @@ int main() FILE *pcm_file; // 输出PCM文件 int err; // 用于存储错误码 - playback = play_start("hw:0,0", SND_PCM_FORMAT_S16_LE, 2, 44100, &period); + playback = play_open("hw:0,0", SND_PCM_FORMAT_S16_LE, 2, 44100, &period); if (!playback) { return 1; @@ -99,7 +96,7 @@ int main() buffer = (char *) malloc(snd_pcm_frames_to_bytes(playback, period)); // 分配缓冲区 if (!buffer) { perror("malloc"); - play_stop(playback); + play_close(playback); return 1; } @@ -108,7 +105,7 @@ int main() if (!pcm_file) { perror("Error opening output file"); free(buffer); // 释放缓冲区 - play_stop(playback); // 关闭PCM设备 + play_close(playback); // 关闭PCM设备 return 1; } @@ -141,7 +138,9 @@ int main() // 清理资源 free(buffer); // 释放缓冲区 fclose(pcm_file); // 关闭文件 - play_stop(playback); + play_close(playback); return 0; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/1/play.h b/1/play.h new file mode 100644 index 0000000..c0f13a9 --- /dev/null +++ b/1/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 \ No newline at end of file diff --git a/1/record.c b/1/record.c new file mode 100644 index 0000000..8364102 --- /dev/null +++ b/1/record.c @@ -0,0 +1,132 @@ +#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 \ No newline at end of file diff --git a/1/record.h b/1/record.h new file mode 100644 index 0000000..648801d --- /dev/null +++ b/1/record.h @@ -0,0 +1,16 @@ +#ifndef RECORD_H +#define ERCORD_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/1/stt.c b/1/stt.c new file mode 100644 index 0000000..e549e2a --- /dev/null +++ b/1/stt.c @@ -0,0 +1,157 @@ +#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; +} + +// 发送请求消息,并添加了dev_pid参数 +// token: 获取的access token +// audio: 音频文件内容 +// size: 音频文件大小 +// dev_pid: 设备ID,用于指定识别的语言,例如1537为中文,1737为英文 +// return: 响应消息正文, NULL 表示失败 +static char* send_request(char* token, char* audio, size_t size, char* dev_pid) { + // 根据dev_pid构建URL + char* url = NULL; + asprintf(&url, "http://vop.baidu.com/server_api?cuid=hqyj&token=%s&dev_pid=%s", token, dev_pid); + + // 创建HTTP头部,说明发送的数据是PCM格式的音频,采样率为16000 + struct curl_slist* headers = NULL; + headers = curl_slist_append(headers, "Content-Type: audio/pcm; rate=16000"); + + // 发送POST请求,音频数据作为请求体 + 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* dev_pid) +{ + 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, dev_pid); + free(token); + if (!response) { + fprintf(stderr, "调用百度语音识别 API 失败\n"); + return NULL; + } + + //处理服务器返回的响应消息 + text = process_response(response); + + free(response); + + return text; +} \ No newline at end of file diff --git a/1/stt.h b/1/stt.h new file mode 100644 index 0000000..b5b2c7a --- /dev/null +++ b/1/stt.h @@ -0,0 +1,8 @@ +#ifndef STT_H +#define STT_H + +#include + +char* speech_to_text(char* audio, size_t size, char* dev_pid); + +#endif \ No newline at end of file diff --git a/1/wake.c b/1/wake.c deleted file mode 100644 index 068b695..0000000 --- a/1/wake.c +++ /dev/null @@ -1,112 +0,0 @@ -#include "snowboy/snowboy-detect-c-wrapper.h" -#include -#include -#include -#include "record.h" - -#define SILENCE_THRESHOLD 5 // 静音阈值秒数 - -int main() -{ - // 创建 Snowboy 检测器 - SnowboyDetect* detector = SnowboyDetectConstructor("common.res", "model.pmdl"); - if (!detector) - { - return EXIT_FAILURE; // 如果检测器创建失败,退出程序 - } - - // 设置 Snowboy 的检测灵敏度 - SnowboyDetectSetSensitivity(detector, "0.5"); - - // 获取检测器支持的音频数据参数 - 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); // 销毁 Snowboy 检测器 - return EXIT_FAILURE; // 如果分配缓冲区失败,退出程序 - } - - FILE* outputFile = NULL; // 用于保存录音文件的文件指针 - time_t silenceStartTime = 0; // 静音开始时间 - int recording = 0; // 录音状态标志 - - while (1) - { - // 从 PCM 设备读取数据 - 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); // 尝试恢复 PCM 设备 - continue; - } - - // 运行 Snowboy 检测 - int status = SnowboyDetectRunDetection(detector, (int16_t*)buffer, snd_pcm_frames_to_bytes(capture, frames) / sizeof(int16_t), 0); - - if (status > 0) - { - // 检测到唤醒词 - if (!recording) { - printf("检测到唤醒词\n"); - recording = 1; // 设置录音状态 - silenceStartTime = 0; // 重置静音开始时间 - // 打开文件以保存录音数据 - outputFile = fopen("output.pcm", "wb"); - if (!outputFile) { - perror("fopen"); - free(buffer); // 释放缓冲区 - record_close(capture); // 关闭音频采集设备 - SnowboyDetectDestructor(detector); // 销毁 Snowboy 检测器 - return EXIT_FAILURE; // 如果打开文件失败,退出程序 - } - } - } - - if (recording) { - // 将音频数据写入文件 - fwrite(buffer, 1, snd_pcm_frames_to_bytes(capture, frames), outputFile); - - // 检测静音状态 - if (status == -2) { - if (silenceStartTime == 0) { - silenceStartTime = time(NULL); // 记录静音开始时间 - } else if (time(NULL) - silenceStartTime >= SILENCE_THRESHOLD) { - // 如果静音持续时间超过阈值,停止录音 - printf("检测到连续%d秒静音,停止录音\n", SILENCE_THRESHOLD); - fclose(outputFile); // 关闭文件 - outputFile = NULL; - recording = 0; // 重置录音状态 - silenceStartTime = 0; // 重置静音开始时间 - } - } else { - silenceStartTime = 0; // 如果有声音,则重置静音开始时间 - } - } - } - - free(buffer); // 释放缓冲区 - record_close(capture); // 关闭音频采集设备 - SnowboyDetectDestructor(detector); // 销毁 Snowboy 检测器 - - return EXIT_SUCCESS; // 正常退出程序 -} diff --git a/1/wakechat.c b/1/wakechat.c deleted file mode 100644 index 9275f05..0000000 --- a/1/wakechat.c +++ /dev/null @@ -1,269 +0,0 @@ -#include -#include -#include "record.h" -#include "stt.h" -#include "config.h" -#include "http.h" -#include -#include "snowboy/snowboy-detect-c-wrapper.h" - -char* app_id = "4535fac4-d8ae-41fe-91c5-2889ff07c006"; - -// 创建会话 -char* create_conversation(char* authtoken) -{ - char* url = "https://qianfan.baidubce.com/v2/app/conversation"; - - 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); - 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 -char* chat(char* authtoken, char* conv_id, char* query) -{ - char* url = "https://qianfan.baidubce.com/v2/app/conversation/runs"; - - 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); - 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; -} - -int main() -{ - SnowboyDetect* detector = SnowboyDetectConstructor("common.res", "model.pmdl"); - if (!detector) - { - return EXIT_FAILURE; - } - - SnowboyDetectSetSensitivity(detector, "0.5"); - - 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; - } - - cJSON* config = read_config("config.json"); - if (!config) - { - free(buffer); - record_close(capture); - SnowboyDetectDestructor(detector); - return EXIT_FAILURE; - } - - cJSON* authtoken = cJSON_GetObjectItem(config, "authtoken"); - if (!authtoken) - { - fprintf(stderr, "配置文件错误: 找不到 'authtoken' 字段\n"); - cJSON_Delete(config); - free(buffer); - record_close(capture); - SnowboyDetectDestructor(detector); - return EXIT_FAILURE; - } - - char* conv_id = create_conversation(authtoken->valuestring); - if (!conv_id) - { - cJSON_Delete(config); - free(buffer); - 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); - if (frames < 0) - { - fprintf(stderr, "Error from read: %s\n", snd_strerror(frames)); - snd_pcm_recover(capture, frames, 0); - continue; - } - - 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) - { - printf("转录文本: %s\n", text); - char* answer = chat(authtoken->valuestring, conv_id, text); - if (answer) - { - printf("大模型回答: %s\n", answer); - free(answer); - } - free(text); - } - snd_pcm_prepare(capture); - } - free(audio); - audio = NULL; - audio_size = 0; - } - - if (memstream) - { - fwrite(buffer, 1, snd_pcm_frames_to_bytes(capture, frames), memstream); - } - } - } - - free(conv_id); - cJSON_Delete(config); - free(buffer); - record_close(capture); - SnowboyDetectDestructor(detector); - - return EXIT_SUCCESS; -} -- Gitee