diff --git a/5/key3.c b/5/key3.c new file mode 100644 index 0000000000000000000000000000000000000000..da8240a92168c1054a82bd6f5954efdd3e8b5186 --- /dev/null +++ b/5/key3.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