From 7518f0e269a5c3d2288676d1615114cc40d16347 Mon Sep 17 00:00:00 2001 From: guojia'nan <1667869633@qq.com> Date: Fri, 5 Jul 2024 16:00:09 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=BA=94=E6=AC=A1=E4=BD=9C=E4=B8=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 7/homework5/include/baidu_cloud.h | 36 +++++++ 7/homework5/include/chat.h | 18 ++++ 7/homework5/include/http.h | 30 ++++++ 7/homework5/include/key1.h | 13 +++ 7/homework5/include/key_common.h | 7 ++ 7/homework5/src/baidu_cloud.c | 131 +++++++++++++++++++++++++ 7/homework5/src/chat.c | 110 +++++++++++++++++++++ 7/homework5/src/http.c | 115 ++++++++++++++++++++++ 7/homework5/src/key1.c | 158 ++++++++++++++++++++++++++++++ 9 files changed, 618 insertions(+) create mode 100644 7/homework5/include/baidu_cloud.h create mode 100644 7/homework5/include/chat.h create mode 100644 7/homework5/include/http.h create mode 100644 7/homework5/include/key1.h create mode 100644 7/homework5/include/key_common.h create mode 100644 7/homework5/src/baidu_cloud.c create mode 100644 7/homework5/src/chat.c create mode 100644 7/homework5/src/http.c create mode 100644 7/homework5/src/key1.c diff --git a/7/homework5/include/baidu_cloud.h b/7/homework5/include/baidu_cloud.h new file mode 100644 index 0000000..a01c812 --- /dev/null +++ b/7/homework5/include/baidu_cloud.h @@ -0,0 +1,36 @@ +#ifndef _BAIDU_CLOUD_H_ +#define _BAIDU_CLOUD_H_ + +/** + * @file baidu_cloud.h + * @brief 包含与百度云API交互的函数,主要用于语音识别功能。 + */ + +/** + * 获取百度云的访问令牌。 + * @param api_key 百度云API Key。 + * @param secret_key 百度云Secret Key。 + * @return 成功返回令牌字符串,失败返回NULL。 + * 使用百度云语音识别之前,必须先调用此函数获取访问令牌。 + */ +char *baidu_get_access_token(const char *api_key, const char *secret_key); + +/** + * 从语音识别响应中提取识别结果。 + * @param response 服务器返回的JSON响应。 + * @return 成功返回识别的文本,失败返回NULL。 + * 该函数解析JSON格式的响应,提取出语音识别的文本结果。 + */ +char *extract_result(const char *response); + +/** + * 使用百度云语音识别服务。 + * @param filename 包含录音数据的文件名。 + * @param token 访问令牌。 + * @param response_code 用于存储HTTP响应代码的变量的地址。 + * @return 成功返回识别结果,失败返回NULL。 + * 此函数读取音频文件数据,发送到百度云进行语音识别,并返回识别结果。 + */ +char *baidu_recognize_speech(const char *filename, const char *token, long *response_code); + +#endif // _BAIDU_CLOUD_H_ diff --git a/7/homework5/include/chat.h b/7/homework5/include/chat.h new file mode 100644 index 0000000..77ad864 --- /dev/null +++ b/7/homework5/include/chat.h @@ -0,0 +1,18 @@ +#ifndef _CHAT_H_ +#define _CHAT_H_ + +/** + * 使用百度云大模型进行对话 + * @param input_text 输入的对话文本 + * @return 成功返回大模型的回复,失败返回NULL + */ +char *chat_with_baidu_model(const char *input_text); + +/** + * 创建百度云大模型会话 + * @param authtoken 认证令牌 + * @return 成功返回会话ID,失败返回NULL + */ +char *create_conversation(const char *authtoken); + +#endif // _CHAT_H_ diff --git a/7/homework5/include/http.h b/7/homework5/include/http.h new file mode 100644 index 0000000..0d6f9c6 --- /dev/null +++ b/7/homework5/include/http.h @@ -0,0 +1,30 @@ +#ifndef _HTTP_H_ +#define _HTTP_H_ + +/** + * @file http.h + * @brief 提供基本的HTTP GET和POST请求功能。 + */ + +#include +#include + +/** + * 发送HTTP GET请求。 + * @param url 请求的URL。 + * @return 成功返回响应内容字符串,失败返回NULL。 + */ +char *http_get(const char *url); + +/** + * 发送HTTP POST请求。 + * @param url 请求的URL。 + * @param data 发送的数据。 + * @param data_size 发送数据的大小。 + * @param response_code 用于存储HTTP响应代码的变量的地址。 + * @param headers 自定义请求头。 + * @return 成功返回响应内容字符串,失败返回NULL。 + */ +char *http_post(const char *url, const char *data, size_t data_size, long *response_code, struct curl_slist *headers); + +#endif // _HTTP_H_ diff --git a/7/homework5/include/key1.h b/7/homework5/include/key1.h new file mode 100644 index 0000000..7f19514 --- /dev/null +++ b/7/homework5/include/key1.h @@ -0,0 +1,13 @@ +#ifndef _KEY1_H_ +#define _KEY1_H_ + +/** + * 按键1控制录音 + * @param pcm_device: PCM设备名 + * @param channels: 声道数 + * @param sample_rate: 采样率 + * @param filename: 输出文件名 + */ +void key1_record_control(const char *pcm_device, unsigned int channels, unsigned int sample_rate, const char *filename); + +#endif // _KEY1_H_ diff --git a/7/homework5/include/key_common.h b/7/homework5/include/key_common.h new file mode 100644 index 0000000..7381214 --- /dev/null +++ b/7/homework5/include/key_common.h @@ -0,0 +1,7 @@ +#ifndef _KEY_COMMON_H_ +#define _KEY_COMMON_H_ +#include + +struct gpiod_line *gpio_init(const char *chip_label, unsigned int line_num, const char *consumer); + +#endif // _KEY_COMMON_H_ diff --git a/7/homework5/src/baidu_cloud.c b/7/homework5/src/baidu_cloud.c new file mode 100644 index 0000000..9f9443d --- /dev/null +++ b/7/homework5/src/baidu_cloud.c @@ -0,0 +1,131 @@ +#include "baidu_cloud.h" +#include "http.h" +#include +#include +#include +#include + +#define TOKEN_URL "https://aip.baidubce.com/oauth/2.0/token" +#define ASR_URL "https://vop.baidu.com/server_api" + +/** + * 获取百度云的访问令牌 + * @param api_key 百度云API Key + * @param secret_key 百度云Secret Key + * @return 成功返回令牌字符串,失败返回NULL + */ +char *baidu_get_access_token(const char *api_key, const char *secret_key) +{ + // 构建URL用于获取访问令牌 + char url[1024]; + snprintf(url, sizeof(url), "%s?grant_type=client_credentials&client_id=%s&client_secret=%s", TOKEN_URL, api_key, secret_key); + + // 发送HTTP GET请求 + char *response = http_get(url); + if (!response) + { + fprintf(stderr, "Failed to get response for token request\n"); + return NULL; + } + + // 解析JSON响应以获取访问令牌 + cJSON *json = cJSON_Parse(response); + if (!json) + { + fprintf(stderr, "Failed to parse JSON from token response\n"); + free(response); + return NULL; + } + + cJSON *token = cJSON_GetObjectItem(json, "access_token"); + if (!token || !cJSON_IsString(token)) + { + fprintf(stderr, "Access token not found in response\n"); + cJSON_Delete(json); + free(response); + return NULL; + } + + // 复制令牌字符串 + char *access_token = strdup(token->valuestring); + cJSON_Delete(json); + free(response); + return access_token; +} + +/** + * 从语音识别响应中提取识别结果 + * @param response 服务器返回的JSON响应 + * @return 成功返回识别的文本,失败返回NULL + */ +char *extract_result(const char *response) +{ + cJSON *json = cJSON_Parse(response); + if (!json) + { + fprintf(stderr, "解析JSON失败\n"); + return NULL; + } + + cJSON *result = cJSON_GetObjectItemCaseSensitive(json, "result"); + if (!cJSON_IsArray(result) || cJSON_GetArraySize(result) == 0) + { + cJSON_Delete(json); + fprintf(stderr, "结果数组不存在或为空\n"); + return NULL; + } + + cJSON *first_result = cJSON_GetArrayItem(result, 0); + if (!cJSON_IsString(first_result)) + { + cJSON_Delete(json); + fprintf(stderr, "结果格式错误\n"); + return NULL; + } + + char *recognition_result = strdup(first_result->valuestring); + cJSON_Delete(json); + return recognition_result; +} + +/** + * 使用百度云语音识别服务 + * @param filename 包含录音数据的文件名 + * @param token 访问令牌 + * @param response_code 用于存储HTTP响应代码的变量的地址 + * @return 成功返回识别结果,失败返回NULL + */ +char *baidu_recognize_speech(const char *filename, const char *token, long *response_code) +{ + FILE *file = fopen(filename, "rb"); + if (!file) + { + perror("Failed to open file for reading"); + return NULL; + } + + fseek(file, 0, SEEK_END); + size_t size = ftell(file); + rewind(file); + + char *data = malloc(size); + if (!data) + { + fclose(file); + fprintf(stderr, "Failed to allocate memory for audio data\n"); + return NULL; + } + + fread(data, 1, size, file); + fclose(file); + + char url[1024]; + snprintf(url, sizeof(url), "%s?token=%s&cuid=7smartspeaker", ASR_URL, token); + + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Content-Type: audio/pcm; rate=16000"); + + char *response = http_post(url, data, size, response_code, headers); + free(data); + return response; +} diff --git a/7/homework5/src/chat.c b/7/homework5/src/chat.c new file mode 100644 index 0000000..89e155c --- /dev/null +++ b/7/homework5/src/chat.c @@ -0,0 +1,110 @@ +#include "chat.h" +#include "http.h" +#include +#include +#include +#include +#include + +#define BAIDU_CHAT_API_URL "https://qianfan.baidubce.com/v2/app/conversation/runs" +#define CREATE_CONVERSATION_URL "https://qianfan.baidubce.com/v2/app/conversation" +#define AUTH_TOKEN "bce-v3/ALTAK-QNxARmIMxHrOMzFsRt2rj/f13f9a0170bc57f0ffc72d36d732696ed0f25510" // 确定的auth token +#define APP_ID "63576019-3feb-423c-bdfc-31e85acff3fd" + +char *create_conversation(const char *authtoken) { + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Content-Type: application/json;charset=utf-8"); + + char auth_header[256]; + snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer %s", authtoken); + headers = curl_slist_append(headers, auth_header); + + cJSON *json_request = cJSON_CreateObject(); + cJSON_AddStringToObject(json_request, "app_id", APP_ID); + char *request_body = cJSON_PrintUnformatted(json_request); + + long response_code; + char *response = http_post(CREATE_CONVERSATION_URL, request_body, strlen(request_body), &response_code, headers); + + free(request_body); + cJSON_Delete(json_request); + + if (response_code != 200 || !response) { + fprintf(stderr, "Failed to create conversation\n"); + free(response); + return NULL; + } + + cJSON *json_response = cJSON_Parse(response); + free(response); + if (!json_response) { + fprintf(stderr, "Failed to parse JSON response\n"); + return NULL; + } + + cJSON *conversation_id = cJSON_GetObjectItemCaseSensitive(json_response, "conversation_id"); + char *retval = NULL; + if (cJSON_IsString(conversation_id) && conversation_id->valuestring != NULL) { + retval = strdup(conversation_id->valuestring); + } else { + fprintf(stderr, "conversation_id 字段不存在或为空\n"); + } + + cJSON_Delete(json_response); + return retval; +} + +char *chat_with_baidu_model(const char *input_text) { + char *conversation_id = create_conversation(AUTH_TOKEN); + if (!conversation_id) { + return NULL; + } + + char url[1024]; + snprintf(url, sizeof(url), "%s", BAIDU_CHAT_API_URL); + + cJSON *json_request = cJSON_CreateObject(); + cJSON_AddStringToObject(json_request, "app_id", APP_ID); + cJSON_AddStringToObject(json_request, "query", input_text); + cJSON_AddBoolToObject(json_request, "stream", false); + cJSON_AddStringToObject(json_request, "conversation_id", conversation_id); + + char *request_body = cJSON_PrintUnformatted(json_request); + long response_code; + + struct curl_slist *headers = NULL; + char auth_header[256]; + snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer %s", AUTH_TOKEN); + headers = curl_slist_append(headers, auth_header); + headers = curl_slist_append(headers, "Content-Type: application/json"); + + char *response = http_post(url, request_body, strlen(request_body), &response_code, headers); + + free(conversation_id); + free(request_body); + cJSON_Delete(json_request); + + if (response_code != 200 || !response) { + fprintf(stderr, "Failed to get response from Baidu Chat API\n"); + free(response); + return NULL; + } + + cJSON *json_response = cJSON_Parse(response); + free(response); + if (!json_response) { + fprintf(stderr, "Failed to parse JSON response\n"); + return NULL; + } + + cJSON *text = cJSON_GetObjectItemCaseSensitive(json_response, "answer"); + printf("%s/n",text); + + char *reply_text = NULL; + if (cJSON_IsString(text) && text->valuestring != NULL) { + reply_text = strdup(text->valuestring); + } + + cJSON_Delete(json_response); + return reply_text; +} diff --git a/7/homework5/src/http.c b/7/homework5/src/http.c new file mode 100644 index 0000000..75b2b1c --- /dev/null +++ b/7/homework5/src/http.c @@ -0,0 +1,115 @@ +#include "http.h" +#include +#include +#include + +struct Memory +{ + char *response; // 动态分配的缓冲区存储HTTP响应数据 + size_t size; // 缓冲区大小 +}; + +// CURL写数据的回调函数 +static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) +{ + size_t real_size = size * nmemb; // 计算实际数据块大小 + struct Memory *mem = (struct Memory *)userp; // 类型转换,指向传入的缓冲区 + + // 重新分配内存以存储新数据(当前大小 + 新数据块大小 + 1) + char *ptr = realloc(mem->response, mem->size + real_size + 1); + if (!ptr) + { + printf("Not enough memory (realloc returned NULL)\n"); + return 0; // 如果内存不足,返回0告知CURL停止操作 + } + + mem->response = ptr; // 更新指针 + memcpy(&(mem->response[mem->size]), contents, real_size); // 将新数据拷贝到缓冲区末尾 + mem->size += real_size; // 更新缓冲区大小 + mem->response[mem->size] = '\0'; // 确保缓冲区以空字符结束 + return real_size; // 返回实际写入的数据大小 +} + +/** + * 发送HTTP GET请求 + * @param url 请求的URL + * @return 成功返回响应内容,失败返回NULL + */ +char *http_get(const char *url) +{ + CURL *curl; + CURLcode res; + struct Memory chunk = {0}; // 初始化动态字符串结构 + + chunk.response = malloc(1); // 初始化为最小大小,足以存储'\0' + chunk.size = 0; // 初始化大小为0 + + curl = curl_easy_init(); // 初始化CURL会话 + if (curl) + { + curl_easy_setopt(curl, CURLOPT_URL, url); // 设置请求的URL + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); // 设置写数据的回调函数 + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); // 设置写数据的用户参数 + + res = curl_easy_perform(curl); // 执行请求 + if (res != CURLE_OK) + { // 检查请求是否成功 + fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); + free(chunk.response); // 如果请求失败,释放内存 + chunk.response = NULL; + } + + curl_easy_cleanup(curl); // 清理CURL资源 + } + + return chunk.response; // 返回响应内容,或者在失败时为NULL +} + +/** + * 发送HTTP POST请求。 + * @param url 请求的URL。 + * @param data 发送的数据。 + * @param data_size 发送数据的大小。 + * @param response_code 用于存储HTTP响应代码的变量的地址。 + * @param headers 自定义请求头。 + * @return 成功返回响应内容字符串,失败返回NULL。 + */ +char *http_post(const char *url, const char *data, size_t data_size, long *response_code, struct curl_slist *headers) +{ + CURL *curl; + CURLcode res; + struct Memory chunk = {0}; + + chunk.response = malloc(1); + chunk.size = 0; + + curl = curl_easy_init(); + if (curl) + { + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data_size); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) + { + fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); + free(chunk.response); + chunk.response = NULL; + } + + if (response_code) + { + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, response_code); + } + + curl_easy_cleanup(curl); + curl_slist_free_all(headers); + } + + return chunk.response; +} \ No newline at end of file diff --git a/7/homework5/src/key1.c b/7/homework5/src/key1.c new file mode 100644 index 0000000..0d46377 --- /dev/null +++ b/7/homework5/src/key1.c @@ -0,0 +1,158 @@ +#include "key1.h" +#include "key_common.h" +#include "record.h" +#include +#include +#include + +#define GPIO_CHIP_LABEL "GPIOF" +#define GPIO_LINE 9 +#define CONSUMER "key1" +#define API_KEY "1sy2fb2e6j7dmZqX9siYj6aa" +#define SECRET_KEY "uj7d4Z2XmdhMvum6qEzOcbsEMxtTC1iX" + +typedef struct +{ + const char *pcm_device; + unsigned int channels; + unsigned int sample_rate; + const char *filename; +} key1_thread_args_t; + +/** + * 按键1录音控制线程函数 + * @param args: 线程参数 + * @return: NULL + */ +static void *key1_thread_func(void *args) +{ + key1_thread_args_t *thread_args = (key1_thread_args_t *)args; + + // 初始化音频捕捉上下文 + audio_record_context_t *context = audio_record_init(thread_args->pcm_device, thread_args->channels, thread_args->sample_rate, thread_args->filename); + if (!context) + { + fprintf(stderr, "初始化音频捕捉上下文失败\n"); + free(thread_args); // 释放内存 + return NULL; + } + + // 初始化GPIO按键 + struct gpiod_line *line = gpio_init(GPIO_CHIP_LABEL, GPIO_LINE, CONSUMER); + if (!line) + { + audio_record_cleanup(context); // 清理音频捕捉上下文 + free(thread_args); // 释放内存 + return NULL; + } + + pthread_detach(pthread_self()); // 在资源初始化后分离线程 + + // 获取初始按键状态 + int last_value = gpiod_line_get_value(line); + + // 获取百度云Access Token + char *token = baidu_get_access_token(API_KEY, SECRET_KEY); + + // 持续监控按键状态 + while (1) + { + int value = gpiod_line_get_value(line); + + if (value != last_value) + { + if (value == 0) + { + printf("按键按下,开始录音\n"); + audio_record_reinit(context, thread_args->pcm_device, thread_args->channels, thread_args->sample_rate, thread_args->filename); + audio_record_start(context); + } + else + { + printf("按键释放,停止录音\n"); + audio_record_stop(context); + + printf("开始语音识别...\n"); + long response_code = 0; + char *recognition_result = baidu_recognize_speech(thread_args->filename, token, &response_code); + if (recognition_result != NULL) + { + if (response_code == 200) + { + char *result = extract_result(recognition_result); + if (result) + { + printf("识别结果:%s\n", result); + + // 与大模型对话 + char *reply = chat_with_baidu_model(result); + if (reply) + { + printf("大模型回复:%s\n", reply); + free(reply); + } + else + { + fprintf(stderr, "与大模型对话失败\n"); + } + + free(result); + } + else + { + fprintf(stderr, "Failed to extract result\n"); + } + } + else + { + fprintf(stderr, "HTTP request failed with code %ld\n", response_code); + } + free(recognition_result); + } + else + { + fprintf(stderr, "Failed to recognize speech\n"); + } + } + last_value = value; + } + usleep(100000); // 延迟100毫秒 + } + + // 关闭GPIO芯片 + gpiod_chip_close(gpiod_line_get_chip(line)); + audio_record_cleanup(context); // 清理音频捕捉上下文 + free(thread_args); // 释放内存 + return NULL; +} + +/** + * 按键1控制录音 + * @param pcm_device: PCM设备名 + * @param channels: 声道数 + * @param sample_rate: 采样率 + * @param filename: 输出文件名 + */ +void key1_record_control(const char *pcm_device, unsigned int channels, unsigned int sample_rate, const char *filename) +{ + pthread_t key1_thread; + key1_thread_args_t *args = (key1_thread_args_t *)malloc(sizeof(key1_thread_args_t)); + if (!args) + { + perror("内存分配失败"); + return; + } + + args->pcm_device = pcm_device; + args->channels = channels; + args->sample_rate = sample_rate; + args->filename = filename; + + // 创建按键1录音控制线程 + if (pthread_create(&key1_thread, NULL, key1_thread_func, args) != 0) + { + perror("创建按键1录音控制线程失败"); + free(args); // 如果线程创建失败,释放内存 + return; + } +} -- Gitee