From dc0d637c6e1ad1f45f788a723d2d92ad435fdfeb Mon Sep 17 00:00:00 2001 From: joysuff <338421459@qq.com> Date: Mon, 1 Jul 2024 20:04:44 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=A3=80=E6=B5=8B?= =?UTF-8?q?=E5=88=B0=E6=8F=90=E7=A4=BA=E8=AF=8D=E5=BD=95=E9=9F=B3=EF=BC=8C?= =?UTF-8?q?=E9=9D=99=E9=9F=B3=E4=BA=94=E7=A7=92=E5=90=8E=E5=81=9C=E6=AD=A2?= =?UTF-8?q?=E5=BD=95=E9=9F=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 15 +++-- CMakePresets.json | 12 ---- key.c | 68 +++++++++++++++++------ record.c | 123 ++++++++++++++++++++++++----------------- record.h | 22 +++----- wake.c | 138 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 280 insertions(+), 98 deletions(-) create mode 100644 wake.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 01d4d4f..ffe59bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,9 @@ cmake_minimum_required(VERSION 3.0.0) project(voice_assistant VERSION 0.1.0 LANGUAGES C) -add_library(record STATIC record.c) -add_library(control STATIC control.c) +# add_library(record STATIC record.c) + +# add_library(control STATIC control.c) add_executable(voice_assistant main.c) @@ -10,5 +11,11 @@ add_executable(voice_assistant main.c) add_executable(play play.c) target_link_libraries(play asound) -add_executable(key key.c) -target_link_libraries(key gpiod record asound control) \ No newline at end of file +add_executable(key key.c record.c control.c) +target_link_libraries(key gpiod asound) + +add_subdirectory(snowboy) + + +add_executable(wake wake.c record.c) +target_link_libraries(wake snowboy-wrapper snowboy-detect cblas m stdc++ asound) \ No newline at end of file diff --git a/CMakePresets.json b/CMakePresets.json index a70520b..baac006 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -24,18 +24,6 @@ "CMAKE_CXX_COMPILER": "/usr/bin/arm-linux-gnueabihf-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/key.c b/key.c index c6903d2..11ad126 100644 --- a/key.c +++ b/key.c @@ -12,13 +12,19 @@ #define GPIO_LINE_KEY2 7 #define GPIO_LINE_KEY3 8 + int main(void) { struct gpiod_chip *chip; struct gpiod_line *line_key1, *line_key2, *line_key3; int value_key1, last_value_key1; int value_key2, last_value_key2; int value_key3, last_value_key3; - pid_t record_pid = -1; + + snd_pcm_uframes_t period; //周期大小 + snd_pcm_t* capture; //音频采集设备 + char* buffer = NULL; //存放音频数据的缓冲区 + FILE* fp = NULL; //录音文件 + // 打开GPIO芯片 chip = gpiod_chip_open_by_label("GPIOF"); @@ -58,28 +64,56 @@ int main(void) { value_key2 = gpiod_line_get_value(line_key2); value_key3 = gpiod_line_get_value(line_key3); - // 检测key1的变化 if (value_key1 != last_value_key1) { + // 如果当前值为0,表示按键被按下 if (value_key1 == 0) { - printf("key1 pressed\n"); - // 启动record进程 - record_pid = fork(); - if (record_pid == 0) { - signal(SIGTERM, handle_sigterm); - start_recording("hw:0,1", "output.pcm", SND_PCM_FORMAT_S16_LE, 2, 44100); - exit(0); + printf("key pressed\n"); + capture = record_open("hw:0,1", SND_PCM_FORMAT_S16_LE, 2, 44100, &period); + if (!capture) + { + continue; } - } else { - if (record_pid > 0) { - stop_recording(); - kill(record_pid, SIGTERM); - waitpid(record_pid, NULL, 0); - record_pid = -1; + buffer = malloc(snd_pcm_frames_to_bytes(capture, period)); // 分配缓冲区 + if (!buffer) + { + perror("malloc"); + record_close(capture); + continue; + } + + fp = fopen("output.pcm", "wb"); + if (!fp) + { + perror("Error opening output file"); + free(buffer); // 释放缓冲区 + record_close(capture); // 关闭PCM设备 + continue; } + } + // 如果当前值为1,表示按键被释放 + else { + printf("key released\n"); + record_close(capture); + capture = NULL; } - last_value_key1 = value_key1; + // 更新上一次的值为当前值 + last_value_key1= value_key1; } + if (value_key1 == 0 && capture) + { + //如果按键按下并且音频采集设备已打开 + snd_pcm_sframes_t frames = snd_pcm_readi(capture, buffer, period); // 从PCM设备读取数据 + if (frames < 0) + { + fprintf(stderr, "Error from read: %s\n", snd_strerror(frames)); + snd_pcm_recover(capture, frames, 0); + } + + fwrite(buffer, snd_pcm_frames_to_bytes(capture, frames), 1, fp); // 将读取的数据写入文件 + } + + // 检测key2的变化 if (value_key2 != last_value_key2) { if (value_key2 == 0) { @@ -107,7 +141,7 @@ int main(void) { } // 延时100毫秒,防止检测过于频繁 - usleep(100000); + // usleep(100000); } // 关闭GPIO芯片 diff --git a/record.c b/record.c index 398e1b5..cdcdb7b 100644 --- a/record.c +++ b/record.c @@ -1,44 +1,50 @@ +#include +#include +#include // 用于处理错误码 #include "record.h" -snd_pcm_t *capture = NULL; -char *buffer = NULL; -FILE *pcm_file = NULL; -volatile sig_atomic_t keep_recording = 1; - -void handle_sigterm(int sig) { - keep_recording = 0; -} - -// 开始录音 -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; - snd_pcm_hw_params_t *params; - int err; +//开始录音 +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); @@ -46,71 +52,84 @@ snd_pcm_t* record_start(const char* name, snd_pcm_format_t format, unsigned int } 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 start_recording(const char *pcm_device, const char *output_file, snd_pcm_format_t format, unsigned int channels, unsigned int rate) { - snd_pcm_uframes_t period; - int err; +//停止录音 +void record_close(snd_pcm_t* capture) +{ + snd_pcm_drain(capture); // 排空PCM设备 + snd_pcm_close(capture); // 关闭PCM设备 +} - capture = record_start(pcm_device, format, channels, rate, &period); - if (!capture) { - exit(1); +#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: %lu frames\n", period); + printf("period: %d frames\n", period); - buffer = (char *) malloc(snd_pcm_frames_to_bytes(capture, period)); + buffer = (char *) malloc(snd_pcm_frames_to_bytes(capture, period)); // 分配缓冲区 if (!buffer) { perror("malloc"); - snd_pcm_close(capture); - exit(1); + record_close(capture); + return 1; } - pcm_file = fopen(output_file, "wb"); + // 打开输出文件 + pcm_file = fopen("output.pcm", "wb"); if (!pcm_file) { perror("Error opening output file"); - free(buffer); - snd_pcm_close(capture); - exit(1); + free(buffer); // 释放缓冲区 + record_close(capture); // 关闭PCM设备 + return 1; } - printf("Recording... Release the button to stop.\n"); - while (keep_recording) { - snd_pcm_sframes_t frames = snd_pcm_readi(capture, buffer, period); - if (frames < 0) { + // 录制数据 + 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); - } + snd_pcm_recover(capture, frames, 0); } - fwrite(buffer, snd_pcm_frames_to_bytes(capture, frames), 1, pcm_file); + fwrite(buffer, snd_pcm_frames_to_bytes(capture, frames), 1, pcm_file); // 将读取的数据写入文件 } -} -void stop_recording() { // 清理资源 - if (buffer) { - free(buffer); - buffer = NULL; - } - if (pcm_file) { - fclose(pcm_file); - pcm_file = NULL; - } - if (capture) { - snd_pcm_drain(capture); - snd_pcm_close(capture); - capture = NULL; - } - printf("Recording stopped.\n"); + free(buffer); // 释放缓冲区 + fclose(pcm_file); // 关闭文件 + record_close(capture); + + return 0; } +#endif diff --git a/record.h b/record.h index 7d732c8..6500100 100644 --- a/record.h +++ b/record.h @@ -1,20 +1,16 @@ #ifndef RECORD_H #define RECORD_H -#include -#include #include -#include -#include -extern snd_pcm_t *capture; -extern char *buffer; -extern FILE *pcm_file; -extern volatile sig_atomic_t keep_recording; +//开始录音 +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 handle_sigterm(int sig); -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 start_recording(const char *pcm_device, const char *output_file, snd_pcm_format_t format, unsigned int channels, unsigned int rate); -void stop_recording(); +//停止录音 +void record_close(snd_pcm_t* capture); -#endif // RECORD_H +#endif \ No newline at end of file diff --git a/wake.c b/wake.c new file mode 100644 index 0000000..0cb58aa --- /dev/null +++ b/wake.c @@ -0,0 +1,138 @@ +#include "snowboy/snowboy-detect-c-wrapper.h" +#include +#include + +#include +#include // 用于 sleep 函数 + +#include "record.h" + +#define TIME_THRESHOLD 5 // 5秒 + +int main() +{ + snd_pcm_uframes_t period; // 周期大小 + snd_pcm_t *capture; // 音频采集设备 + char *buffer = NULL; // 存放音频数据的缓冲区 + FILE *fp = NULL; // 录音文件 + + time_t statusTimestamp = 0; + int statusIsMinusTwo = 0; + + // 创建snowboy检测器 + SnowboyDetect *detector = SnowboyDetectConstructor("common.res", "model.pmdl"); + if (!detector) + { + return EXIT_FAILURE; + } + + SnowboyDetectSetSensitivity(detector, "0.53"); + // SnowboyDetectSetAudioGain(detector, 1.0); + + // 获取检测器支持的音频数据参数 + // 采样深度 + int bits = SnowboyDetectBitsPerSample(detector); + // 声道数量 + int channels = SnowboyDetectNumChannels(detector); + // 采样频率 + int rate = SnowboyDetectSampleRate(detector); + + printf("采样深度: %d\n", bits); + printf("声道数量: %d\n", channels); + printf("采样频率: %d\n", rate); + + // 打开音频采集设备 + period = 999; + capture = record_open("default", SND_PCM_FORMAT_S16_LE, channels, rate, &period); + if (!capture) + { + return EXIT_FAILURE; + } + + buffer = malloc(snd_pcm_frames_to_bytes(capture, period)); // 分配缓冲区 + if (!buffer) + { + perror("malloc"); + record_close(capture); + SnowboyDetectDestructor(detector); + return EXIT_FAILURE; + } + + fp = fopen("output.pcm", "wb"); + if (!fp) + { + perror("Error opening output file"); + free(buffer); // 释放缓冲区 + record_close(capture); // 关闭PCM设备 + return EXIT_FAILURE; + } + + 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); + } + + //-2: 表示静音 + //-1: 检测出错 + // 0: 有声音,但不是唤醒词 + //>0: 检测到唤醒词 + int status = SnowboyDetectRunDetection(detector, (int16_t *)buffer, snd_pcm_frames_to_bytes(capture, frames) / sizeof(int16_t), 0); + printf("%d\n", status); + + if (status > 0) + { + printf("检测到唤醒词\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, fp); // 将读取的数据写入文件 + status = SnowboyDetectRunDetection(detector, (int16_t *)buffer, snd_pcm_frames_to_bytes(capture, frames) / sizeof(int16_t), 0); + if (status == -2) + { + if (!statusIsMinusTwo) + { + statusIsMinusTwo = 1; + statusTimestamp = time(NULL); // 记录当前时间 + } + } + else + { + statusIsMinusTwo = 0; // 重置标记 + statusTimestamp = 0; // 重置时间戳 + } + + if (statusIsMinusTwo) + { + time_t currentTime = time(NULL); // 获取当前时间 + double timeDiff = difftime(currentTime, statusTimestamp); // 计算时间差 + if (timeDiff > TIME_THRESHOLD) + { + printf("Status has been -2 for more than %d seconds.\n", TIME_THRESHOLD); + record_close(capture); + capture = NULL; + break; + } + else + { + printf("Status has been -2 for %.2f seconds.\n", timeDiff); + } + } + } + break; + } + } + free(buffer); // 释放分配的缓冲区 + + SnowboyDetectDestructor(detector); + + return EXIT_SUCCESS; +} \ No newline at end of file -- Gitee From cb7875e63989baf1f5925c375b1aad40e9a983f2 Mon Sep 17 00:00:00 2001 From: joysuff <338421459@qq.com> Date: Thu, 4 Jul 2024 14:44:10 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=99=BE=E5=BA=A6?= =?UTF-8?q?=E4=BA=91api=E5=AE=9E=E7=8E=B0=E8=AF=AD=E9=9F=B3=E8=AF=86?= =?UTF-8?q?=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.c | 26 ++++++++++ config.h | 9 ++++ http.c | 110 +++++++++++++++++++++++++++++++++++++++ http.h | 9 ++++ stt.c | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ token.c | 67 ++++++++++++++++++++++++ token.h | 7 +++ 7 files changed, 383 insertions(+) create mode 100644 config.c create mode 100644 config.h create mode 100644 http.c create mode 100644 http.h create mode 100644 stt.c create mode 100644 token.c create mode 100644 token.h diff --git a/config.c b/config.c new file mode 100644 index 0000000..c7b7973 --- /dev/null +++ b/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/config.h b/config.h new file mode 100644 index 0000000..fd3546b --- /dev/null +++ b/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/http.c b/http.c new file mode 100644 index 0000000..161c102 --- /dev/null +++ b/http.c @@ -0,0 +1,110 @@ +#include +#include "http.h" + +//发送GET请求 +//url: 请求地址 +//headers: 增加的请求头部字段 +//psize: 响应正文大小,输出参数 +//返回值: 响应正文,需要调用者释放内存 +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: 请求正文 +//psize: 请求和响应正文大小,输入和输出参数 +//返回值: 响应正文,需要调用者释放内存 +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; +} \ No newline at end of file diff --git a/http.h b/http.h new file mode 100644 index 0000000..59c962c --- /dev/null +++ b/http.h @@ -0,0 +1,9 @@ +#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); + +#endif diff --git a/stt.c b/stt.c new file mode 100644 index 0000000..c95753e --- /dev/null +++ b/stt.c @@ -0,0 +1,155 @@ +#include +#include +#include "config.h" +#include "token.h" +#include "http.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 表示失败 +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) +{ + //解析 JSON 响应 + cJSON *json = cJSON_ParseWithLength(response, size); + if (!json) { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return; + } + + //判断err_no字段 + cJSON* err_no = cJSON_GetObjectItem(json, "err_no"); + if (!err_no) { + fprintf(stderr, "err_no 字段不存在\n"); + cJSON_Delete(json); + return; + } + //判断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; + } + + // 获取 "result" 字段中的第一个元素 + cJSON *result = cJSON_GetObjectItem(json, "result"); + if (!result) { + fprintf(stderr, "JSON 格式错误: 找不到'result' 字段\n"); + cJSON_Delete(json); + return; + } + + if (cJSON_GetArraySize(result) > 0) { + // 获取第一个元素的 "content" 字段 + cJSON *content = cJSON_GetArrayItem(result, 0); + //打印结果 + printf("result: %s\n", content->valuestring); + } + + cJSON_Delete(json); +} +#if 1 +int main() +{ + + //读取音频文件 + size_t size; + char* buffer = load_audio_file("16k.pcm", &size); + if (!buffer) { + return EXIT_FAILURE; + } + + //读取配置信息,API KEY 和 SECRET KEY + cJSON *config = read_config("config.json"); + if (!config) { + printf("config: %s\n", cJSON_Print(config)); + free(buffer); + return EXIT_FAILURE; + } + cJSON* api_key = cJSON_GetObjectItem(config, "api_key"); + cJSON* secret_key = cJSON_GetObjectItem(config, "secret_key"); + if (!api_key ||!secret_key) { + fprintf(stderr, "配置文件错误: 找不到 'api_key' 或'secret_key' 字段\n"); + cJSON_Delete(config); + free(buffer); + return EXIT_FAILURE; + } + + //获取token + char* token = get_access_token(api_key->valuestring, secret_key->valuestring); + cJSON_Delete(config); + if (!token) { + fprintf(stderr, "获取 token 失败\n"); + free(buffer); + return EXIT_FAILURE; + } + + //调用百度语音识别 API + char* response = send_request(token, buffer, size); + free(buffer); + free(token); + if (!response) { + fprintf(stderr, "调用百度语音识别 API 失败\n"); + return EXIT_FAILURE; + } + + //处理服务器返回的响应消息 + process_response(response, size); + + free(response); + + return EXIT_SUCCESS; +} + +#endif \ No newline at end of file diff --git a/token.c b/token.c new file mode 100644 index 0000000..06d69b8 --- /dev/null +++ b/token.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include "http.h" +#include "config.h" +#include "token.h" + +//成功返回access token,失败返回NULL +char *get_access_token(const char *ak, const char *sk) +{ + char* token = NULL; + + //设置URL + char* url = "https://aip.baidubce.com/oauth/2.0/token"; + char* form = NULL; + asprintf(&form, "grant_type=client_credentials&client_id=%s&client_secret=%s&", ak, sk); + + //发送请求报文 + size_t size = strlen(form); + char* response = post(url, NULL, form, &size); + free(form); + + if (!response) + { + return NULL; + } + + //解析响应报文 + cJSON* root = cJSON_ParseWithLength(response, size); + free(response); + + if (!root) + { + // 解析错误 + const char *error_ptr = cJSON_GetErrorPtr(); + if (error_ptr != NULL) + { + fprintf(stderr, "Error before: %s\n", error_ptr); + } + return NULL; + } + + cJSON* access_token = cJSON_GetObjectItem(root, "access_token"); + if (!access_token) + { + fprintf(stderr, "access_token attribute not found\n"); + cJSON_Delete(root); + return NULL; + } + + if (!cJSON_IsString(access_token)) + { + fprintf(stderr, "access_token attribute format error\n"); + cJSON_Delete(root); + return NULL; + } + + token = strdup(access_token->valuestring); + + // 删除解析后对象占用的内存 + cJSON_Delete(root); + + return token; +} + diff --git a/token.h b/token.h new file mode 100644 index 0000000..5b2442b --- /dev/null +++ b/token.h @@ -0,0 +1,7 @@ +#ifndef TOKEN_H +#define TOKEN_H + +//成功返回access token,失败返回NULL +char *get_access_token(const char *ak, const char *sk); + +#endif -- Gitee From 163cbe4b1666a4ba4b9e9cdf2c6dcb973fc4fece Mon Sep 17 00:00:00 2001 From: joysuff <338421459@qq.com> Date: Thu, 4 Jul 2024 14:45:22 +0800 Subject: [PATCH 3/8] =?UTF-8?q?=E8=B0=83=E7=94=A8=E4=BB=8A=E6=97=A5?= =?UTF-8?q?=E8=AF=97=E8=AF=8Dapi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jrsc.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 jrsc.c diff --git a/jrsc.c b/jrsc.c new file mode 100644 index 0000000..6ffbec5 --- /dev/null +++ b/jrsc.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include + +// 回调函数,用于处理服务器响应数据 +size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) { + size_t totalSize = size * nmemb; + strncat(userp, contents, totalSize); + return totalSize; +} + +// 打印解析的 JSON 数据 +void print_parsed_data(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; + } + // 获取并打印 "region" 字符串 + cJSON *region = cJSON_GetObjectItemCaseSensitive(data, "region"); + if (cJSON_IsString(region) && (region->valuestring != NULL)) { + printf("Region: %s\n", region->valuestring); + } else { + fprintf(stderr, "JSON 格式错误: 找不到 'region' 字符串\n"); + } + + // 获取并打印 "weatherData" 对象 + cJSON *weatherData = cJSON_GetObjectItemCaseSensitive(data, "weatherData"); + if (cJSON_IsObject(weatherData)) { + cJSON *temperature = cJSON_GetObjectItemCaseSensitive(weatherData, "temperature"); + if (cJSON_IsNumber(temperature)) { + printf("Temperature: %d\n", temperature->valueint); + } + cJSON *weather = cJSON_GetObjectItemCaseSensitive(weatherData, "weather"); + if (cJSON_IsString(weather) && (weather->valuestring != NULL)) { + printf("Weather: %s\n", weather->valuestring); + } + // 其他天气数据可以按类似方式提取和打印 + } else { + fprintf(stderr, "JSON 格式错误: 找不到 'weatherData' 对象\n"); + } + // 释放 JSON 对象 + cJSON_Delete(json); +} + +int main(void) { + CURL *client; + CURLcode err; + char buffer[8192] = {0}; // 用于存储响应数据 + + // 初始化 CURL + curl_global_init(CURL_GLOBAL_ALL); + client = curl_easy_init(); + + // 设置 CURL 选项 + curl_easy_setopt(client, CURLOPT_URL, "https://v2.jinrishici.com/info"); + curl_easy_setopt(client, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(client, CURLOPT_WRITEDATA, buffer); + + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "X-User-Token: o9M10gtneoIxRLk5UO13fUG7guYxnS7h"); // 替换 YOUR_API_KEY 为你的 API Key + curl_easy_setopt(client, CURLOPT_HTTPHEADER, headers); + + // 执行 CURL 请求 + err = curl_easy_perform(client); + + if (err != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() 失败: %s\n", curl_easy_strerror(err)); + } else { + // // 打印响应数据 + // printf("Response data: %s\n", buffer); + + // 解析并打印 JSON 数据 + print_parsed_data(buffer, strlen(buffer)); + } + + // 清理资源 + curl_slist_free_all(headers); + curl_easy_cleanup(client); + curl_global_cleanup(); + + return 0; +} \ No newline at end of file -- Gitee From adfda74cbefc468145b951605949592f81e62a87 Mon Sep 17 00:00:00 2001 From: joysuff <338421459@qq.com> Date: Thu, 4 Jul 2024 14:47:52 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=8C=89=E9=94=AE?= =?UTF-8?q?=E5=BD=95=E9=9F=B3=E7=BB=93=E6=9D=9F=E5=90=8E=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E7=99=BE=E5=BA=A6api=E8=BF=9B=E8=A1=8C=E8=AF=AD=E9=9F=B3?= =?UTF-8?q?=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- key.c | 221 ++++++++++++++++++++++++++++++++++++++++++++++++++------- wake.c | 2 +- 2 files changed, 197 insertions(+), 26 deletions(-) diff --git a/key.c b/key.c index 11ad126..22a9042 100644 --- a/key.c +++ b/key.c @@ -8,27 +8,134 @@ #include "record.h" #include "control.h" +#include "config.h" +#include "token.h" +#include "http.h" + #define GPIO_LINE_KEY1 9 #define GPIO_LINE_KEY2 7 #define GPIO_LINE_KEY3 8 +// 读取音频文件 +// file: 音频文件路径 +// size: 音频文件大小 +// return: 音频文件内容, NULL 表示失败 +char *load_audio_file(const char *file, size_t *size) +{ + // 打开音频文件 + FILE *fp = fopen(file, "rb"); + if (!fp) + { + perror(file); + return NULL; + } + // 获取文件大小 + fseek(fp, 0, SEEK_END); + *size = ftell(fp); + fseek(fp, 0, SEEK_SET); + // 读取文件内容 + char *buffer = (char *)malloc(*size); + if (!buffer) + { + perror("malloc"); + fclose(fp); + return NULL; + } + fread(buffer, 1, *size, fp); + fclose(fp); + return buffer; +} + +// 发送请求消息 +// token: 获取的access token +// audio: 音频文件内容 +// size: 音频文件大小 +// return: 响应消息正文, NULL 表示失败 +char *send_request(char *token, char *audio, size_t size) +{ + char *url = NULL; + asprintf(&url, "http://vop.baidu.com/server_api?cuid=hqyj&token=%s", token); + + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Content-Type: audio/pcm; rate=16000"); + + char *response = post(url, headers, audio, &size); -int main(void) { + free(url); + curl_slist_free_all(headers); + + return response; +} + +// 处理服务器返回的响应消息 +void process_response(char *response, size_t size) +{ + // 解析 JSON 响应 + cJSON *json = cJSON_ParseWithLength(response, size); + if (!json) + { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return; + } + + // 判断err_no字段 + cJSON *err_no = cJSON_GetObjectItem(json, "err_no"); + if (!err_no) + { + fprintf(stderr, "err_no 字段不存在\n"); + cJSON_Delete(json); + return; + } + // 判断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; + } + + // 获取 "result" 字段中的第一个元素 + cJSON *result = cJSON_GetObjectItem(json, "result"); + if (!result) + { + fprintf(stderr, "JSON 格式错误: 找不到'result' 字段\n"); + cJSON_Delete(json); + return; + } + + if (cJSON_GetArraySize(result) > 0) + { + // 获取第一个元素的 "content" 字段 + 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_key1, *line_key2, *line_key3; int value_key1, last_value_key1; int value_key2, last_value_key2; int value_key3, last_value_key3; - snd_pcm_uframes_t period; //周期大小 - snd_pcm_t* capture; //音频采集设备 - char* buffer = NULL; //存放音频数据的缓冲区 - FILE* fp = NULL; //录音文件 - + snd_pcm_uframes_t period; // 周期大小 + snd_pcm_t *capture; // 音频采集设备 + char *buffer = NULL; // 存放音频数据的缓冲区 + FILE *fp = NULL; // 录音文件 // 打开GPIO芯片 chip = gpiod_chip_open_by_label("GPIOF"); - if (!chip) { + if (!chip) + { perror("打开GPIO芯片失败"); return 1; } @@ -37,7 +144,8 @@ int main(void) { line_key1 = gpiod_chip_get_line(chip, GPIO_LINE_KEY1); line_key2 = gpiod_chip_get_line(chip, GPIO_LINE_KEY2); line_key3 = gpiod_chip_get_line(chip, GPIO_LINE_KEY3); - if (!line_key1 || !line_key2 || !line_key3) { + if (!line_key1 || !line_key2 || !line_key3) + { perror("获取GPIO线失败"); gpiod_chip_close(chip); return 1; @@ -46,7 +154,8 @@ int main(void) { // 将GPIO线设置为输入模式 if (gpiod_line_request_input(line_key1, "key1") || gpiod_line_request_input(line_key2, "key2") || - gpiod_line_request_input(line_key3, "key3")) { + gpiod_line_request_input(line_key3, "key3")) + { perror("请求将GPIO线设置为输入模式失败"); gpiod_chip_close(chip); return 1; @@ -58,17 +167,20 @@ int main(void) { last_value_key3 = gpiod_line_get_value(line_key3); // 无限循环检测GPIO线值的变化 - while (1) { + while (1) + { // 获取当前的GPIO线值 value_key1 = gpiod_line_get_value(line_key1); value_key2 = gpiod_line_get_value(line_key2); value_key3 = gpiod_line_get_value(line_key3); - if (value_key1 != last_value_key1) { + if (value_key1 != last_value_key1) + { // 如果当前值为0,表示按键被按下 - if (value_key1 == 0) { + if (value_key1 == 0) + { printf("key pressed\n"); - capture = record_open("hw:0,1", SND_PCM_FORMAT_S16_LE, 2, 44100, &period); + capture = record_open("hw:0,1", SND_PCM_FORMAT_S16_LE, 1, 16000, &period); if (!capture) { continue; @@ -85,24 +197,76 @@ int main(void) { if (!fp) { perror("Error opening output file"); - free(buffer); // 释放缓冲区 + free(buffer); // 释放缓冲区 record_close(capture); // 关闭PCM设备 continue; } - } + } // 如果当前值为1,表示按键被释放 - else { + else + { printf("key released\n"); record_close(capture); capture = NULL; + + // 读取音频文件 + size_t size; + char *buffer = load_audio_file("output.pcm", &size); + if (!buffer) + { + return EXIT_FAILURE; + } + + // 读取配置信息,API KEY 和 SECRET KEY + cJSON *config = read_config("config.json"); + if (!config) + { + printf("config: %s\n", cJSON_Print(config)); + free(buffer); + return EXIT_FAILURE; + } + cJSON *api_key = cJSON_GetObjectItem(config, "api_key"); + cJSON *secret_key = cJSON_GetObjectItem(config, "secret_key"); + if (!api_key || !secret_key) + { + fprintf(stderr, "配置文件错误: 找不到 'api_key' 或'secret_key' 字段\n"); + cJSON_Delete(config); + free(buffer); + return EXIT_FAILURE; + } + + // 获取token + char *token = get_access_token(api_key->valuestring, secret_key->valuestring); + cJSON_Delete(config); + if (!token) + { + fprintf(stderr, "获取 token 失败\n"); + free(buffer); + return EXIT_FAILURE; + } + + // 调用百度语音识别 API + char *response = send_request(token, buffer, size); + free(buffer); + free(token); + if (!response) + { + fprintf(stderr, "调用百度语音识别 API 失败\n"); + return EXIT_FAILURE; + } + + // 处理服务器返回的响应消息 + process_response(response, size); + + free(response); } // 更新上一次的值为当前值 - last_value_key1= value_key1; + last_value_key1 = value_key1; } if (value_key1 == 0 && capture) { - //如果按键按下并且音频采集设备已打开 + // 如果按键按下并且音频采集设备已打开 snd_pcm_sframes_t frames = snd_pcm_readi(capture, buffer, period); // 从PCM设备读取数据 if (frames < 0) { @@ -113,12 +277,15 @@ int main(void) { fwrite(buffer, snd_pcm_frames_to_bytes(capture, frames), 1, fp); // 将读取的数据写入文件 } - // 检测key2的变化 - if (value_key2 != last_value_key2) { - if (value_key2 == 0) { + if (value_key2 != last_value_key2) + { + if (value_key2 == 0) + { printf("key2 pressed\n"); - } else { + } + else + { long volume = get_playback_volume("hw:0", "Analog"); volume += 5; set_playback_volume("hw:0", "Analog", volume); @@ -128,10 +295,14 @@ int main(void) { } // 检测key3的变化 - if (value_key3 != last_value_key3) { - if (value_key3 == 0) { + if (value_key3 != last_value_key3) + { + if (value_key3 == 0) + { printf("key3 pressed\n"); - } else { + } + else + { long volume = get_playback_volume("hw:0", "Analog"); volume -= 5; set_playback_volume("hw:0", "Analog", volume); diff --git a/wake.c b/wake.c index 0cb58aa..f59b37b 100644 --- a/wake.c +++ b/wake.c @@ -26,7 +26,7 @@ int main() return EXIT_FAILURE; } - SnowboyDetectSetSensitivity(detector, "0.53"); + SnowboyDetectSetSensitivity(detector, "0.5"); // SnowboyDetectSetAudioGain(detector, 1.0); // 获取检测器支持的音频数据参数 -- Gitee From 8369047af7f68a7f6be9088c6dca5fb8caeb1d7c Mon Sep 17 00:00:00 2001 From: joysuff <338421459@qq.com> Date: Thu, 4 Jul 2024 14:48:48 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=E6=9B=B4=E6=96=B0CmakeList=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ffe59bc..9c8eed5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,11 +11,20 @@ add_executable(voice_assistant main.c) add_executable(play play.c) target_link_libraries(play asound) -add_executable(key key.c record.c control.c) -target_link_libraries(key gpiod asound) +add_executable(key key.c record.c control.c token.c http.c config.c) +target_compile_definitions(key PRIVATE _GNU_SOURCE) +target_link_libraries(key gpiod asound cjson curl) add_subdirectory(snowboy) add_executable(wake wake.c record.c) -target_link_libraries(wake snowboy-wrapper snowboy-detect cblas m stdc++ asound) \ No newline at end of file +target_link_libraries(wake snowboy-wrapper snowboy-detect cblas m stdc++ asound) + + +add_executable(jrsc jrsc.c) +target_link_libraries(jrsc cjson curl) + +add_executable(stt stt.c token.c http.c config.c) +target_compile_definitions(stt PRIVATE _GNU_SOURCE) +target_link_libraries(stt curl cjson) \ No newline at end of file -- Gitee From ec57da7c0e139a72610557be13d6ade561f790ac Mon Sep 17 00:00:00 2001 From: joysuff <338421459@qq.com> Date: Fri, 5 Jul 2024 13:43:31 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=E8=B0=83=E7=94=A8=E7=99=BE=E5=BA=A6?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E8=87=AA=E5=BB=BAAI=E5=BA=94=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 11 +-- chat.c | 179 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 5 deletions(-) create mode 100644 chat.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c8eed5..3d9b715 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,13 +18,14 @@ target_link_libraries(key gpiod asound cjson curl) add_subdirectory(snowboy) -add_executable(wake wake.c record.c) -target_link_libraries(wake snowboy-wrapper snowboy-detect cblas m stdc++ asound) +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) +target_compile_definitions(wake PRIVATE _GNU_SOURCE) add_executable(jrsc jrsc.c) target_link_libraries(jrsc cjson curl) -add_executable(stt stt.c token.c http.c config.c) -target_compile_definitions(stt PRIVATE _GNU_SOURCE) -target_link_libraries(stt curl cjson) \ No newline at end of file +add_executable(chat chat.c config.c http.c) +target_link_libraries(chat cjson curl) +target_compile_definitions(chat PRIVATE _GNU_SOURCE) \ No newline at end of file diff --git a/chat.c b/chat.c new file mode 100644 index 0000000..fb98b8b --- /dev/null +++ b/chat.c @@ -0,0 +1,179 @@ +//调用百度云千帆AppBuilder API进行对话 + +#include +#include +#include +#include +#include "config.h" +#include "http.h" + +char* app_id = "62b84ef5-5daf-491d-97a8-5966fb4deb34"; + +//创建会话 +char* create_conversation(char* authtoken) +{ + char* url = "https://qianfan.baidubce.com/v2/app/conversation"; + + //拼接Authorization字段 + char* auth; + asprintf(&auth, "Authorization: Bearer %s", authtoken); + + //添加请求头部字段 + struct curl_slist* headers = NULL; + headers = curl_slist_append(headers, "Content-Type: application/json;charset=utf-8"); + headers = curl_slist_append(headers, auth); + + //准备请求正文 + cJSON* obj = cJSON_CreateObject(); + cJSON_AddStringToObject(obj, "app_id", app_id); + //将JSON对象转换为JSON字符串 + char* json = cJSON_Print(obj); + + //发送请求 + size_t size = strlen(json); + char* response = 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; +} + +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; +} -- Gitee From 3488ce4242e95d05afd3a64c93bb6c2868226865 Mon Sep 17 00:00:00 2001 From: joysuff <338421459@qq.com> Date: Fri, 5 Jul 2024 14:41:22 +0800 Subject: [PATCH 7/8] =?UTF-8?q?=E8=AF=AD=E9=9F=B3=E8=BD=AC=E6=96=87?= =?UTF-8?q?=E5=AD=97=E7=9B=B8=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- key.c | 174 ++++++++++++++++++++++++++++++++++---- stt.c | 60 +++++++------ stt.h | 8 ++ wake.c | 259 ++++++++++++++++++++++++++++++++++++++++++++------------- 4 files changed, 394 insertions(+), 107 deletions(-) create mode 100644 stt.h diff --git a/key.c b/key.c index 22a9042..9f5c0ec 100644 --- a/key.c +++ b/key.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include "record.h" @@ -16,6 +17,130 @@ #define GPIO_LINE_KEY2 7 #define GPIO_LINE_KEY3 8 +char *app_id = "62b84ef5-5daf-491d-97a8-5966fb4deb34"; + +// 创建会话 +char *create_conversation(char *authtoken) +{ + char *url = "https://qianfan.baidubce.com/v2/app/conversation"; + + // 拼接Authorization字段 + char *auth; + asprintf(&auth, "Authorization: Bearer %s", authtoken); + + // 添加请求头部字段 + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Content-Type: application/json;charset=utf-8"); + headers = curl_slist_append(headers, auth); + + // 准备请求正文 + cJSON *obj = cJSON_CreateObject(); + cJSON_AddStringToObject(obj, "app_id", app_id); + // 将JSON对象转换为JSON字符串 + char *json = cJSON_Print(obj); + + // 发送请求 + size_t size = strlen(json); + char *response = 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; +} + // 读取音频文件 // file: 音频文件路径 // size: 音频文件大小 @@ -68,14 +193,14 @@ char *send_request(char *token, char *audio, size_t size) } // 处理服务器返回的响应消息 -void process_response(char *response, size_t size) +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; + return NULL; } // 判断err_no字段 @@ -84,8 +209,9 @@ void process_response(char *response, size_t size) { fprintf(stderr, "err_no 字段不存在\n"); cJSON_Delete(json); - return; + return NULL; } + // 判断err_no的值 if (err_no->valueint != 0) { @@ -96,7 +222,7 @@ void process_response(char *response, size_t size) fprintf(stderr, "err_msg: %s\n", err_msg->valuestring); } cJSON_Delete(json); - return; + return NULL; } // 获取 "result" 字段中的第一个元素 @@ -105,18 +231,25 @@ void process_response(char *response, size_t size) { fprintf(stderr, "JSON 格式错误: 找不到'result' 字段\n"); cJSON_Delete(json); - return; + return NULL; } if (cJSON_GetArraySize(result) > 0) { // 获取第一个元素的 "content" 字段 cJSON *content = cJSON_GetArrayItem(result, 0); - // 打印结果 - printf("result: %s\n", content->valuestring); + if (content && cJSON_IsString(content)) + { + // 分配内存并返回字符串 + char *content_str = strdup(content->valuestring); + cJSON_Delete(json); + return content_str; + } } + // 释放 JSON 对象 cJSON_Delete(json); + return NULL; } int main(void) @@ -127,10 +260,10 @@ int main(void) int value_key2, last_value_key2; int value_key3, last_value_key3; - snd_pcm_uframes_t period; // 周期大小 - snd_pcm_t *capture; // 音频采集设备 - char *buffer = NULL; // 存放音频数据的缓冲区 - FILE *fp = NULL; // 录音文件 + snd_pcm_uframes_t period = 999; // 周期大小 + snd_pcm_t *capture; // 音频采集设备 + char *buffer = NULL; // 存放音频数据的缓冲区 + FILE *fp = NULL; // 录音文件 // 打开GPIO芯片 chip = gpiod_chip_open_by_label("GPIOF"); @@ -227,17 +360,17 @@ int main(void) } cJSON *api_key = cJSON_GetObjectItem(config, "api_key"); cJSON *secret_key = cJSON_GetObjectItem(config, "secret_key"); - if (!api_key || !secret_key) + cJSON *authtoken = cJSON_GetObjectItem(config, "authtoken"); + if (!api_key || !secret_key || !authtoken) { - fprintf(stderr, "配置文件错误: 找不到 'api_key' 或'secret_key' 字段\n"); + fprintf(stderr, "配置文件错误: 找不到 'api_key' 或'secret_key'或'authtoken'字段\n"); cJSON_Delete(config); free(buffer); return EXIT_FAILURE; } - // 获取token char *token = get_access_token(api_key->valuestring, secret_key->valuestring); - cJSON_Delete(config); + if (!token) { fprintf(stderr, "获取 token 失败\n"); @@ -254,10 +387,17 @@ int main(void) fprintf(stderr, "调用百度语音识别 API 失败\n"); return EXIT_FAILURE; } - // 处理服务器返回的响应消息 - process_response(response, size); + char *text = process_response(response, size); + printf(">%s\n", text); + printf("authtoken:%s\n", authtoken->valuestring); + // 创建会话 + char *conv_id = create_conversation(authtoken->valuestring); + printf("conv_id: %s\n", conv_id); + char *answer = chat(authtoken->valuestring, conv_id, text); + printf("< %s\n", answer); + cJSON_Delete(config); free(response); } // 更新上一次的值为当前值 diff --git a/stt.c b/stt.c index c95753e..78c54ff 100644 --- a/stt.c +++ b/stt.c @@ -1,8 +1,10 @@ #include #include +#include //strdup #include "config.h" #include "token.h" #include "http.h" +#include "stt.h" //读取音频文件 //file: 音频文件路径 @@ -37,7 +39,7 @@ char* load_audio_file(const char* file, size_t* size) //audio: 音频文件内容 //size: 音频文件大小 //return: 响应消息正文, NULL 表示失败 -char* send_request(char* token, char* audio, size_t size) +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); @@ -54,13 +56,15 @@ char* send_request(char* token, char* audio, size_t size) } //处理服务器返回的响应消息 -void process_response(char* response, size_t size) +static char* process_response(char* response, size_t size) { + char* retval; + //解析 JSON 响应 cJSON *json = cJSON_ParseWithLength(response, size); if (!json) { fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); - return; + return NULL; } //判断err_no字段 @@ -68,7 +72,7 @@ void process_response(char* response, size_t size) if (!err_no) { fprintf(stderr, "err_no 字段不存在\n"); cJSON_Delete(json); - return; + return NULL; } //判断err_no的值 if (err_no->valueint != 0) { @@ -79,7 +83,7 @@ void process_response(char* response, size_t size) fprintf(stderr, "err_msg: %s\n", err_msg->valuestring); } cJSON_Delete(json); - return; + return NULL; } // 获取 "result" 字段中的第一个元素 @@ -87,43 +91,41 @@ void process_response(char* response, size_t size) if (!result) { fprintf(stderr, "JSON 格式错误: 找不到'result' 字段\n"); cJSON_Delete(json); - return; + return NULL; } if (cJSON_GetArraySize(result) > 0) { // 获取第一个元素的 "content" 字段 cJSON *content = cJSON_GetArrayItem(result, 0); - //打印结果 - printf("result: %s\n", content->valuestring); + //保存结果 + retval = strdup(content->valuestring); } cJSON_Delete(json); -} -#if 1 -int main() -{ - //读取音频文件 - size_t size; - char* buffer = load_audio_file("16k.pcm", &size); - if (!buffer) { - return EXIT_FAILURE; - } + 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)); - free(buffer); - return EXIT_FAILURE; + 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); - free(buffer); - return EXIT_FAILURE; + return NULL; } //获取token @@ -131,25 +133,21 @@ int main() cJSON_Delete(config); if (!token) { fprintf(stderr, "获取 token 失败\n"); - free(buffer); - return EXIT_FAILURE; + return NULL; } //调用百度语音识别 API - char* response = send_request(token, buffer, size); - free(buffer); + char* response = send_request(token, audio, size); free(token); if (!response) { fprintf(stderr, "调用百度语音识别 API 失败\n"); - return EXIT_FAILURE; + return NULL; } //处理服务器返回的响应消息 - process_response(response, size); + text = process_response(response, size); free(response); - return EXIT_SUCCESS; + return text; } - -#endif \ No newline at end of file diff --git a/stt.h b/stt.h new file mode 100644 index 0000000..81c0a30 --- /dev/null +++ b/stt.h @@ -0,0 +1,8 @@ +#ifndef STT_H +#define STT_H + +#include + +char* speech_to_text(char* audio, size_t size); + +#endif diff --git a/wake.c b/wake.c index f59b37b..5ec3bc3 100644 --- a/wake.c +++ b/wake.c @@ -1,24 +1,138 @@ #include "snowboy/snowboy-detect-c-wrapper.h" #include #include +#include +#include "record.h" +#include "stt.h" +#include "config.h" +#include "http.h" -#include -#include // 用于 sleep 函数 +char *app_id = "62b84ef5-5daf-491d-97a8-5966fb4deb34"; -#include "record.h" +// 创建会话 +char *create_conversation(char *authtoken) +{ + char *url = "https://qianfan.baidubce.com/v2/app/conversation"; -#define TIME_THRESHOLD 5 // 5秒 + // 拼接Authorization字段 + char *auth; + asprintf(&auth, "Authorization: Bearer %s", authtoken); -int main() + // 添加请求头部字段 + 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) { - snd_pcm_uframes_t period; // 周期大小 - snd_pcm_t *capture; // 音频采集设备 - char *buffer = NULL; // 存放音频数据的缓冲区 - FILE *fp = NULL; // 录音文件 + 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); - time_t statusTimestamp = 0; - int statusIsMinusTwo = 0; + if (!response) + { + return NULL; + } + + obj = cJSON_ParseWithLength(response, size); + free(response); + + if (!obj) + { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return NULL; + } + cJSON *answer = cJSON_GetObjectItem(obj, "answer"); + if (!answer) + { + fprintf(stderr, "answer 字段不存在\n"); + puts(cJSON_Print(obj)); + cJSON_Delete(obj); + return NULL; + } + + char *retval = strdup(answer->valuestring); + + cJSON_Delete(obj); + + return retval; +} + +int main() +{ // 创建snowboy检测器 SnowboyDetect *detector = SnowboyDetectConstructor("common.res", "model.pmdl"); if (!detector) @@ -26,9 +140,6 @@ int main() return EXIT_FAILURE; } - SnowboyDetectSetSensitivity(detector, "0.5"); - // SnowboyDetectSetAudioGain(detector, 1.0); - // 获取检测器支持的音频数据参数 // 采样深度 int bits = SnowboyDetectBitsPerSample(detector); @@ -42,14 +153,14 @@ int main() printf("采样频率: %d\n", rate); // 打开音频采集设备 - period = 999; - capture = record_open("default", SND_PCM_FORMAT_S16_LE, channels, rate, &period); + 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; } - buffer = malloc(snd_pcm_frames_to_bytes(capture, period)); // 分配缓冲区 + char *buffer = malloc(snd_pcm_frames_to_bytes(capture, period)); // 分配缓冲区 if (!buffer) { perror("malloc"); @@ -58,14 +169,13 @@ int main() return EXIT_FAILURE; } - fp = fopen("output.pcm", "wb"); - if (!fp) - { - perror("Error opening output file"); - free(buffer); // 释放缓冲区 - record_close(capture); // 关闭PCM设备 - return EXIT_FAILURE; - } + int recording = 0; + // 检测到连续的静音次数 + int silence = 0; + // 内存文件 + FILE *memstream = NULL; + char *audio = NULL; + size_t audio_size = 0; while (1) { @@ -74,64 +184,95 @@ int main() { fprintf(stderr, "Error from read: %s\n", snd_strerror(frames)); snd_pcm_recover(capture, frames, 0); + continue; } - //-2: 表示静音 + //-2: 静音 //-1: 检测出错 // 0: 有声音,但不是唤醒词 //>0: 检测到唤醒词 int status = SnowboyDetectRunDetection(detector, (int16_t *)buffer, snd_pcm_frames_to_bytes(capture, frames) / sizeof(int16_t), 0); - printf("%d\n", status); - if (status > 0) { - printf("检测到唤醒词\n"); - while (1) + printf("检测到唤醒词,开始录音\n"); + recording = 1; + // 打开内存文件 + memstream = open_memstream(&audio, &audio_size); + if (!memstream) + { + perror("open_memstream"); + continue; + } + } + if (recording) + { + if (status == -2) { - snd_pcm_sframes_t frames = snd_pcm_readi(capture, buffer, period); // 从PCM设备读取数据 - if (frames < 0) + silence++; + } + + if (status == 0) + { + silence = 0; + } + + if (silence > 32) + { + printf("停止录音\n"); + recording = 0; + silence = 0; + if (memstream) { - fprintf(stderr, "Error from read: %s\n", snd_strerror(frames)); - snd_pcm_recover(capture, frames, 0); + fclose(memstream); + memstream = NULL; } - fwrite(buffer, snd_pcm_frames_to_bytes(capture, frames), 1, fp); // 将读取的数据写入文件 - status = SnowboyDetectRunDetection(detector, (int16_t *)buffer, snd_pcm_frames_to_bytes(capture, frames) / sizeof(int16_t), 0); - if (status == -2) + + if (audio_size) { - if (!statusIsMinusTwo) + // 暂停录音 + snd_pcm_drop(capture); + // 识别语音 + char *text = speech_to_text(audio, audio_size); + if (text) { - statusIsMinusTwo = 1; - statusTimestamp = time(NULL); // 记录当前时间 + puts(text); } - } - else - { - statusIsMinusTwo = 0; // 重置标记 - statusTimestamp = 0; // 重置时间戳 - } - if (statusIsMinusTwo) - { - time_t currentTime = time(NULL); // 获取当前时间 - double timeDiff = difftime(currentTime, statusTimestamp); // 计算时间差 - if (timeDiff > TIME_THRESHOLD) + // 读取配置文件中的平台密钥 + cJSON *config = read_config("config.json"); + if (!config) { - printf("Status has been -2 for more than %d seconds.\n", TIME_THRESHOLD); - record_close(capture); - capture = NULL; - break; + return EXIT_FAILURE; } - else + + cJSON *authtoken = cJSON_GetObjectItem(config, "authtoken"); + if (!authtoken) { - printf("Status has been -2 for %.2f seconds.\n", timeDiff); + 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* answer = chat(authtoken->valuestring, conv_id, text); + printf("< %s\n", answer); + + // 恢复录音 + snd_pcm_prepare(capture); } } - break; + + if (memstream) + { + fwrite(buffer, 1, snd_pcm_frames_to_bytes(capture, frames), memstream); + } } } - free(buffer); // 释放分配的缓冲区 + free(buffer); + record_close(capture); SnowboyDetectDestructor(detector); return EXIT_SUCCESS; -- Gitee From 8badfac4e320072c5fbbbf80b7dde1a565df3878 Mon Sep 17 00:00:00 2001 From: joysuff <338421459@qq.com> Date: Fri, 5 Jul 2024 14:42:06 +0800 Subject: [PATCH 8/8] =?UTF-8?q?=E4=BF=AE=E6=94=B9Cmakelist=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d9b715..2576339 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,4 +28,7 @@ target_link_libraries(jrsc cjson curl) add_executable(chat chat.c config.c http.c) target_link_libraries(chat cjson curl) -target_compile_definitions(chat PRIVATE _GNU_SOURCE) \ No newline at end of file +target_compile_definitions(chat PRIVATE _GNU_SOURCE) + +add_executable(utils utils.c) +target_link_libraries(utils uuid resolv) \ No newline at end of file -- Gitee