From 372c09451d8e99dbfab63a695436e7fcdeeaab77 Mon Sep 17 00:00:00 2001 From: ZJY <1400329747@qq.com> Date: Tue, 2 Jul 2024 14:02:59 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E7=AC=AC=E4=B9=9D=E7=BB=84=E7=AC=AC?= =?UTF-8?q?=E4=BA=8C=E6=AC=A1=E4=BD=9C=E4=B8=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CMakeLists.txt" | 20 + .../control.c" | 434 ++++++++++++++++++ .../key.c" | 106 +++++ .../main.c" | 5 + .../play.c" | 147 ++++++ .../record.c" | 99 ++++ .../record.h" | 18 + .../wake.c" | 107 +++++ 8 files changed, 936 insertions(+) create mode 100644 "\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/CMakeLists.txt" create mode 100644 "\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/control.c" create mode 100644 "\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/key.c" create mode 100644 "\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/main.c" create mode 100644 "\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/play.c" create mode 100644 "\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/record.c" create mode 100644 "\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/record.h" create mode 100644 "\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/wake.c" diff --git "a/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/CMakeLists.txt" "b/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/CMakeLists.txt" new file mode 100644 index 0000000..a95ffce --- /dev/null +++ "b/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/CMakeLists.txt" @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.0.0) +project(voice-assistant VERSION 0.1.0 LANGUAGES C) + +add_executable(voice-assistant main.c) + +add_executable(control control.c) +target_link_libraries(control asound) + +#add_executable(record record.c) +#target_link_libraries(record 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_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/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/control.c" "b/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/control.c" new file mode 100644 index 0000000..af482da --- /dev/null +++ "b/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/control.c" @@ -0,0 +1,434 @@ +#include +#include +#include + +// 设置音频采集开关的函数 +// card: 声卡名称 +// selem: 控制项名称 +// enable: 开关状态 +int set_capture_switch(const char* card, const char* selem, bool enable) { + int err; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + + // 打开混音器 + if ((err = snd_mixer_open(&handle, 0)) < 0) { + fprintf(stderr, "Mixer %s open error: %s\n", card, snd_strerror(err)); + return err; + } + + // 附加控制接口到混音器 + if ((err = snd_mixer_attach(handle, card)) < 0) { + fprintf(stderr, "Mixer attach %s error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 注册混音器 + if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { + fprintf(stderr, "Mixer register error: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 加载混音器元素 + if ((err = snd_mixer_load(handle)) < 0) { + fprintf(stderr, "Mixer %s load error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 分配简单元素ID + snd_mixer_selem_id_alloca(&sid); + + // 设置简单元素的名称 + snd_mixer_selem_id_set_name(sid, selem); + + // 查找简单元素 + snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid); + if (!elem) { + fprintf(stderr, "Unable to find simple control '%s',%i\n", selem, 0); + snd_mixer_close(handle); + return -ENOENT; + } + + // 设置采集开关(启用或禁用) + if ((err = snd_mixer_selem_set_capture_switch_all(elem, enable ? 1 : 0)) < 0) { + fprintf(stderr, "Unable to set capture switch: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 关闭混音器 + snd_mixer_close(handle); + + return 0; // 成功 +} + +// 设置音频回放开关的函数 +// card: 声卡名称 +// selem: 控制项名称 +// enable: 开关状态 +int set_playback_switch(const char* card, const char* selem, bool enable) { + int err; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + + // 打开混音器 + if ((err = snd_mixer_open(&handle, 0)) < 0) { + fprintf(stderr, "Mixer %s open error: %s\n", card, snd_strerror(err)); + return err; + } + + // 附加控制接口到混音器 + if ((err = snd_mixer_attach(handle, card)) < 0) { + fprintf(stderr, "Mixer attach %s error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 注册混音器 + if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { + fprintf(stderr, "Mixer register error: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 加载混音器元素 + if ((err = snd_mixer_load(handle)) < 0) { + fprintf(stderr, "Mixer %s load error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 分配简单元素ID + snd_mixer_selem_id_alloca(&sid); + + // 设置简单元素的名称 + snd_mixer_selem_id_set_name(sid, selem); + + // 查找简单元素 + snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid); + if (!elem) { + fprintf(stderr, "Unable to find simple control '%s',%i\n", selem, 0); + snd_mixer_close(handle); + return -ENOENT; + } + + // 设置回放开关(启用或禁用) + if ((err = snd_mixer_selem_set_playback_switch_all(elem, enable ? 1 : 0)) < 0) { + fprintf(stderr, "Unable to set playback switch: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 关闭混音器 + snd_mixer_close(handle); + + return 0; // 成功 +} + +// 获取音频采集音量 +// card: 声卡名称 +// selem: 控制项名称 +// 返回值: 当前音量 +int get_capture_volume(const char* card, const char* selem) +{ + int err; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + + // 打开混音器 + if ((err = snd_mixer_open(&handle, 0)) < 0) { + fprintf(stderr, "Mixer %s open error: %s\n", card, snd_strerror(err)); + return err; + } + + // 附加控制接口到混音器 + if ((err = snd_mixer_attach(handle, card)) < 0) { + fprintf(stderr, "Mixer attach %s error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 注册混音器 + if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { + fprintf(stderr, "Mixer register error: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 加载混音器元素 + if ((err = snd_mixer_load(handle)) < 0) { + fprintf(stderr, "Mixer %s load error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 分配简单元素ID + snd_mixer_selem_id_alloca(&sid); + + // 设置简单元素的名称 + snd_mixer_selem_id_set_name(sid, selem); + + // 查找简单元素 + snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid); + if (!elem) { + fprintf(stderr, "Unable to find simple control '%s',%i\n", selem, 0); + snd_mixer_close(handle); + return -ENOENT; + } + + // 获取采集通道音量 + long volume = 0; + if ((err = snd_mixer_selem_get_capture_volume(elem, 0, &volume)) < 0) { + fprintf(stderr, "Unable to get capture volume: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 关闭混音器 + snd_mixer_close(handle); + + return volume; // 成功返回音量 +} + +// 获取音频回放通道音量 +// card: 声卡名称 +// selem: 控制项名称 +// 返回值: 当前音量 +int get_playback_volume(const char* card, const char* selem) { + int err; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + + // 打开混音器 + if ((err = snd_mixer_open(&handle, 0)) < 0) { + fprintf(stderr, "Mixer %s open error: %s\n", card, snd_strerror(err)); + return err; + } + + // 附加控制接口到混音器 + if ((err = snd_mixer_attach(handle, card)) < 0) { + fprintf(stderr, "Mixer attach %s error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 注册混音器 + if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { + fprintf(stderr, "Mixer register error: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 加载混音器元素 + if ((err = snd_mixer_load(handle)) < 0) { + fprintf(stderr, "Mixer %s load error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 分配简单元素ID + snd_mixer_selem_id_alloca(&sid); + + // 设置简单元素的名称 + snd_mixer_selem_id_set_name(sid, selem); + + // 查找简单元素 + snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid); + if (!elem) { + fprintf(stderr, "Unable to find simple control '%s',%i\n", selem, 0); + snd_mixer_close(handle); + return -ENOENT; + } + + // 获取回放通道音量 + 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; // 成功返回音量 +} + +// 设置音频采集音量 +// card: 声卡名称 +// selem: 控制项名称 +// volume: 设置音量 +// 返回值: 成功返回设置后的音量,失败返回错误码 +int set_capture_volume(const char* card, const char* selem, long volume) +{ + int err; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + + // 打开混音器 + if ((err = snd_mixer_open(&handle, 0)) < 0) { + fprintf(stderr, "Mixer %s open error: %s\n", card, snd_strerror(err)); + return err; + } + + // 附加控制接口到混音器 + if ((err = snd_mixer_attach(handle, card)) < 0) { + fprintf(stderr, "Mixer attach %s error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 注册混音器 + if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { + fprintf(stderr, "Mixer register error: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 加载混音器元素 + if ((err = snd_mixer_load(handle)) < 0) { + fprintf(stderr, "Mixer %s load error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 分配简单元素ID + snd_mixer_selem_id_alloca(&sid); + + // 设置简单元素的名称 + snd_mixer_selem_id_set_name(sid, selem); + + // 查找简单元素 + snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid); + if (!elem) { + fprintf(stderr, "Unable to find simple control '%s',%i\n", selem, 0); + snd_mixer_close(handle); + return -ENOENT; + } + + // 获取音量范围 + long min, max; + if ((err = snd_mixer_selem_get_capture_volume_range(elem, &min, &max)) < 0) + { + fprintf(stderr, "Unable to get capture volume range: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + if (volume < min) + { + volume = min; + } + + if (volume > max) + { + volume = max; + } + + // 设置采集通道音量 + if ((err = snd_mixer_selem_set_capture_volume_all(elem, volume)) < 0) { + fprintf(stderr, "Unable to set capture volume: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 关闭混音器 + snd_mixer_close(handle); + + return volume; // 成功 +} + +// 设置音频回放通道音量 +// card: 声卡名称 +// selem: 控制项名称 +// volume: 设置音量 +// 返回值: 成功返回设置后的音量,失败返回错误码 +int set_playback_volume(const char* card, const char* selem, long volume) +{ + int err; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + + // 打开混音器 + if ((err = snd_mixer_open(&handle, 0)) < 0) { + fprintf(stderr, "Mixer %s open error: %s\n", card, snd_strerror(err)); + return err; + } + + // 附加控制接口到混音器 + if ((err = snd_mixer_attach(handle, card)) < 0) { + fprintf(stderr, "Mixer attach %s error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 注册混音器 + if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { + fprintf(stderr, "Mixer register error: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 加载混音器元素 + if ((err = snd_mixer_load(handle)) < 0) { + fprintf(stderr, "Mixer %s load error: %s\n", card, snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 分配简单元素ID + snd_mixer_selem_id_alloca(&sid); + + // 设置简单元素的名称 + snd_mixer_selem_id_set_name(sid, selem); + + // 查找简单元素 + snd_mixer_elem_t *elem = snd_mixer_find_selem(handle, sid); + if (!elem) { + fprintf(stderr, "Unable to find simple control '%s',%i\n", selem, 0); + snd_mixer_close(handle); + return -ENOENT; + } + + // 获取音量范围 + long min, max; + if ((err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max)) < 0) + { + fprintf(stderr, "Unable to get playback volume range: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + if (volume < min) + { + volume = min; + } + + if (volume > max) + { + volume = max; + } + + // 设置回放通道音量 + if ((err = snd_mixer_selem_set_playback_volume_all(elem, volume)) < 0) { + fprintf(stderr, "Unable to set playback volume: %s\n", snd_strerror(err)); + snd_mixer_close(handle); + return err; + } + + // 关闭混音器 + snd_mixer_close(handle); + + return volume; // 成功返回设置后音量 +} + +int main(int argc, char** argv) +{ + printf("Playback Vol: %d\n", get_playback_volume("hw:0", "Analog")); + printf("New Playback Vol: %d\n", set_playback_volume("hw:0", "Analog", atoi(argv[1]))); + + return 0; +} \ No newline at end of file diff --git "a/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/key.c" "b/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/key.c" new file mode 100644 index 0000000..0565d25 --- /dev/null +++ "b/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/key.c" @@ -0,0 +1,106 @@ +#include // 包含libgpiod库的头文件 +#include // 包含标准输入输出库 +#include // 包含UNIX标准库,用于usleep函数 +#include "record.h" + +#define GPIO_LINE 9 // 定义GPIO线的编号,这里是PF9 + +int main(void) { + struct gpiod_chip *chip; // 定义指向GPIO芯片的指针 + struct gpiod_line *line; // 定义指向GPIO线的指针 + int value, last_value; // 定义当前值和上一次的值,用于检测状态变化 + 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) { + perror("打开GPIO芯片失败"); + return 1; + } + + // 获取GPIO线 + line = gpiod_chip_get_line(chip, GPIO_LINE); + if (!line) { + perror("获取GPIO线失败"); + gpiod_chip_close(chip); + return 1; + } + + // 将GPIO线设置为输入模式 + if (gpiod_line_request_input(line, "key1")) { + perror("请求将GPIO线设置为输入模式失败"); + gpiod_chip_close(chip); + return 1; + } + + // 获取初始的GPIO线值 + last_value = gpiod_line_get_value(line); + + // 无限循环检测GPIO线值的变化 + while (1) { + // 获取当前的GPIO线值 + value = gpiod_line_get_value(line); + + // 如果当前值与上一次的值不同,说明按键状态发生了变化 + if (value != last_value) { + // 如果当前值为0,表示按键被按下 + if (value == 0) { + printf("key pressed\n"); + capture = record_open("hw:0,1", SND_PCM_FORMAT_S16_LE, 2, 44100, &period); + 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); // 关闭PCM设备 + continue; + } + } + // 如果当前值为1,表示按键被释放 + else { + printf("key released\n"); + record_close(capture); + capture = NULL; + } + // 更新上一次的值为当前值 + last_value = value; + } + + if (value == 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); // 将读取的数据写入文件 + } + // 延时100毫秒,防止检测过于频繁 + //usleep(100000); + } + + // 释放GPIO线资源 + gpiod_line_release(line); + // 关闭GPIO芯片 + gpiod_chip_close(chip); + return 0; +} \ No newline at end of file diff --git "a/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/main.c" "b/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/main.c" new file mode 100644 index 0000000..7790bb1 --- /dev/null +++ "b/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/main.c" @@ -0,0 +1,5 @@ +#include + +int main(){ + printf("Hello, from voice-assistant!\n"); +} diff --git "a/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/play.c" "b/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/play.c" new file mode 100644 index 0000000..f31987b --- /dev/null +++ "b/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/play.c" @@ -0,0 +1,147 @@ +#include +#include +#include +#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) +{ + snd_pcm_t *playback; // PCM设备句柄 + snd_pcm_hw_params_t *params; // PCM硬件参数 + int err; // 用于存储错误码 + int dir; + + // 打开PCM设备用于回放 + if ((err = snd_pcm_open(&playback, name, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf(stderr, "Error opening PCM device %s: %s\n", name, snd_strerror(err)); + return NULL; + } + + // 分配参数对象,并用默认值填充 + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(playback, params); + + // 设置参数 + // 设置访问类型:交错模式 + if ((err = snd_pcm_hw_params_set_access(playback, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Error setting access: %s\n", snd_strerror(err)); + snd_pcm_close(playback); + return NULL; + } + // 设置数据格式:16位小端 + if ((err = snd_pcm_hw_params_set_format(playback, params, format)) < 0) { + fprintf(stderr, "Error setting format: %s\n", snd_strerror(err)); + snd_pcm_close(playback); + return NULL; + } + // 设置声道数:立体声 + if ((err = snd_pcm_hw_params_set_channels(playback, params, channel)) < 0) { + fprintf(stderr, "Error setting channels: %s\n", snd_strerror(err)); + snd_pcm_close(playback); + return NULL; + } + // 设置采样率 + if ((err = snd_pcm_hw_params_set_rate_near(playback, params, &rate, &dir)) < 0) { + fprintf(stderr, "Error setting rate: %s\n", snd_strerror(err)); + snd_pcm_close(playback); + return NULL; + } + + printf("sample rate: %d Hz\n", rate); + + //设置周期大小 + if ((err = snd_pcm_hw_params_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); + + return playback; +} + +//停止播放 +void play_close(snd_pcm_t* playback) +{ + snd_pcm_drain(playback); // 排空PCM设备 + snd_pcm_close(playback); // 关闭PCM设备 +} + +int main() +{ + snd_pcm_t *playback; // PCM设备句柄 + snd_pcm_uframes_t period = 999; // 每个周期的帧数 + char *buffer; // 缓冲区,用于存储从文件中读取的音频数据 + FILE *pcm_file; // 输出PCM文件 + int err; // 用于存储错误码 + + playback = play_open("hw:0,0", SND_PCM_FORMAT_S16_LE, 2, 44100, &period); + if (!playback) + { + return 1; + } + + printf("period: %d frames\n", period); + + buffer = (char *) malloc(snd_pcm_frames_to_bytes(playback, period)); // 分配缓冲区 + if (!buffer) { + perror("malloc"); + play_close(playback); + return 1; + } + + // 打开输出文件 + pcm_file = fopen("output.pcm", "rb"); + if (!pcm_file) { + perror("Error opening output file"); + free(buffer); // 释放缓冲区 + play_close(playback); // 关闭PCM设备 + return 1; + } + + // 录制数据 + printf("Playing... Press Ctrl+C to stop.\n"); + while (1) { + size_t bytes = fread(buffer, 1, snd_pcm_frames_to_bytes(playback, period), pcm_file); + 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); + + return 0; +} \ No newline at end of file diff --git "a/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/record.c" "b/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/record.c" new file mode 100644 index 0000000..d891805 --- /dev/null +++ "b/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/record.c" @@ -0,0 +1,99 @@ +#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设备 +} + +// 开始录音到文件 +FILE* start_recording(const char* filename) +{ + FILE *pcm_file = fopen(filename, "wb"); + if (!pcm_file) { + perror("Error opening output file"); + return NULL; + } + return pcm_file; +} + +// 停止录音到文件 +void stop_recording(FILE* pcm_file) +{ + if (pcm_file) { + fclose(pcm_file); + } +} diff --git "a/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/record.h" "b/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/record.h" new file mode 100644 index 0000000..618391e --- /dev/null +++ "b/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/record.h" @@ -0,0 +1,18 @@ +#ifndef RECORD_H +#define RECORD_H +FILE* start_recording(const char* filename); +void stop_recording(FILE* pcm_file); + +#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/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/wake.c" "b/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/wake.c" new file mode 100644 index 0000000..a0fdc80 --- /dev/null +++ "b/\347\254\254\344\272\214\346\254\241\344\275\234\344\270\232/wake.c" @@ -0,0 +1,107 @@ +#include "snowboy/snowboy-detect-c-wrapper.h" +#include +#include +#include +#include "record.h" + +#define SILENCE_THRESHOLD 5 // 5秒的静音 + +int main() +{ + // 创建snowboy检测器 + SnowboyDetect* detector = SnowboyDetectConstructor("common.res", "laotie.pmdl"); + if (!detector) + { + return EXIT_FAILURE; + } + + // 获取检测器支持的音频数据参数 + int bits = SnowboyDetectBitsPerSample(detector); + int channels = SnowboyDetectNumChannels(detector); + int rate = SnowboyDetectSampleRate(detector); + + printf("采样深度: %d\n", bits); + printf("声道数量: %d\n", channels); + printf("采样频率: %d\n", rate); + + // 打开音频采集设备 + snd_pcm_uframes_t period = 999; + snd_pcm_t* capture = record_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; + } + + int recording = 0; // 录音状态变量 + time_t silence_start = 0; // 静音开始时间变量 + FILE *pcm_file = NULL; // PCM文件指针 + + while (1) + { + snd_pcm_sframes_t frames = snd_pcm_readi(capture, buffer, period); // 从PCM设备读取数据 + if (frames < 0) + { + fprintf(stderr, "Error from read: %s\n", snd_strerror(frames)); + snd_pcm_recover(capture, frames, 0); + continue; + } + + int status = SnowboyDetectRunDetection(detector, (int16_t*)buffer, snd_pcm_frames_to_bytes(capture, frames) / sizeof(int16_t), 0); + + if (status > 0) + { + printf("检测到唤醒词\n"); + if (!recording) + { + printf("开始录音\n"); + pcm_file = start_recording("output.pcm"); + if (!pcm_file) + { + free(buffer); + record_close(capture); + SnowboyDetectDestructor(detector); + return EXIT_FAILURE; + } + recording = 1; + silence_start = 0; // 重置静音时间 + } + } + else if (status == -2 && recording) + { + if (silence_start == 0) + { + silence_start = time(NULL); // 记录静音开始时间 + } + else if (difftime(time(NULL), silence_start) >= SILENCE_THRESHOLD) + { + printf("检测到连续5秒的静音,停止录音\n"); + stop_recording(pcm_file); + recording = 0; + silence_start = 0; + } + } + else + { + silence_start = 0; // 重置静音时间 + } + + if (recording && pcm_file) + { + fwrite(buffer, snd_pcm_frames_to_bytes(capture, frames), 1, pcm_file); // 将录音数据写入文件 + } + } + + free(buffer); + record_close(capture); + SnowboyDetectDestructor(detector); + + return EXIT_SUCCESS; +} -- Gitee From 7456c0032a35aca3c6eb36c31ac0b6fd3ad5900a Mon Sep 17 00:00:00 2001 From: ZJY <1400329747@qq.com> Date: Wed, 3 Jul 2024 13:09:07 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E7=AC=AC=E4=B9=9D=E7=BB=84=E7=AC=AC?= =?UTF-8?q?=E4=B8=89=E6=AC=A1=E4=BD=9C=E4=B8=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CMakeLists.txt" | 23 ++++ .../jrsc.c" | 106 ++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 "\347\254\254\344\270\211\346\254\241\344\275\234\344\270\232/CMakeLists.txt" create mode 100644 "\347\254\254\344\270\211\346\254\241\344\275\234\344\270\232/jrsc.c" diff --git "a/\347\254\254\344\270\211\346\254\241\344\275\234\344\270\232/CMakeLists.txt" "b/\347\254\254\344\270\211\346\254\241\344\275\234\344\270\232/CMakeLists.txt" new file mode 100644 index 0000000..cbd0eac --- /dev/null +++ "b/\347\254\254\344\270\211\346\254\241\344\275\234\344\270\232/CMakeLists.txt" @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.0.0) +project(voice-assistant VERSION 0.1.0 LANGUAGES C) + +add_executable(voice-assistant main.c) + +add_executable(control control.c) +target_link_libraries(control asound) + +#add_executable(record record.c) +#target_link_libraries(record 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_subdirectory(snowboy) +add_executable(wake wake.c record.c) +target_link_libraries(wake snowboy-wrapper snowboy-detect cblas m stdc++ asound) + +add_executable(jrsc jrsc.c) +target_link_libraries(jrsc curl cjson) \ No newline at end of file diff --git "a/\347\254\254\344\270\211\346\254\241\344\275\234\344\270\232/jrsc.c" "b/\347\254\254\344\270\211\346\254\241\344\275\234\344\270\232/jrsc.c" new file mode 100644 index 0000000..d43edf3 --- /dev/null +++ "b/\347\254\254\344\270\211\346\254\241\344\275\234\344\270\232/jrsc.c" @@ -0,0 +1,106 @@ +#include +#include +#include +#include + +// 打印天气情况 +void print_weather_info(const char *response, size_t size) { + // 解析 JSON 响应 + cJSON *json = cJSON_ParseWithLength(response, size); + if (json == NULL) { + fprintf(stderr, "解析 JSON 失败: [%s]\n", cJSON_GetErrorPtr()); + return; + } + + // 获取 "data" 对象 + cJSON *data = cJSON_GetObjectItemCaseSensitive(json, "data"); + if (!cJSON_IsObject(data)) { + fprintf(stderr, "JSON 格式错误: 找不到 'data' 对象\n"); + cJSON_Delete(json); + return; + } + + // 获取 "weatherData" 对象 + cJSON *weatherData = cJSON_GetObjectItemCaseSensitive(data, "weatherData"); + if (!cJSON_IsObject(weatherData)) { + fprintf(stderr, "JSON 格式错误: 找不到 'weatherData' 对象\n"); + cJSON_Delete(json); + return; + } + + // 获取并打印天气信息 + cJSON *temperature = cJSON_GetObjectItemCaseSensitive(weatherData, "temperature"); + cJSON *windDirection = cJSON_GetObjectItemCaseSensitive(weatherData, "windDirection"); + cJSON *windPower = cJSON_GetObjectItemCaseSensitive(weatherData, "windPower"); + cJSON *humidity = cJSON_GetObjectItemCaseSensitive(weatherData, "humidity"); + cJSON *updateTime = cJSON_GetObjectItemCaseSensitive(weatherData, "updateTime"); + cJSON *weather = cJSON_GetObjectItemCaseSensitive(weatherData, "weather"); + cJSON *visibility = cJSON_GetObjectItemCaseSensitive(weatherData, "visibility"); + cJSON *rainfall = cJSON_GetObjectItemCaseSensitive(weatherData, "rainfall"); + cJSON *pm25 = cJSON_GetObjectItemCaseSensitive(weatherData, "pm25"); + + if (cJSON_IsNumber(temperature) && cJSON_IsString(windDirection) && cJSON_IsNumber(windPower) && + cJSON_IsNumber(humidity) && cJSON_IsString(updateTime) && cJSON_IsString(weather) && + cJSON_IsString(visibility) && cJSON_IsNumber(rainfall) && cJSON_IsNumber(pm25)) { + printf("天气信息:\n"); + printf("温度: %.1f°C\n", temperature->valuedouble); + printf("风向: %s\n", windDirection->valuestring); + printf("风力: %d级\n", windPower->valueint); + printf("湿度: %d%%\n", humidity->valueint); + printf("更新时间: %s\n", updateTime->valuestring); + printf("天气: %s\n", weather->valuestring); + printf("能见度: %s\n", visibility->valuestring); + printf("降雨量: %.1fmm\n", rainfall->valuedouble); + printf("PM2.5: %d\n", pm25->valueint); + } else { + fprintf(stderr, "天气信息解析错误\n"); + } + + // 释放 JSON 对象 + cJSON_Delete(json); +} + +int main(void) { + CURL *client; + CURLcode err; + char *response; + size_t size; + + // 创建内存流 + FILE *memstream = open_memstream(&response, &size); + if (memstream == NULL) { + perror("open_memstream"); + return 1; + } + + // 初始化 CURL + curl_global_init(CURL_GLOBAL_ALL); + client = curl_easy_init(); + + // 设置 CURL 选项 + curl_easy_setopt(client, CURLOPT_URL, "https://v2.jinrishici.com/info"); + curl_easy_setopt(client, CURLOPT_WRITEDATA, memstream); + + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "X-User-Token:mSXItltfv6/+nxBxm/du9RLg5LKIyrtu"); // 替换 YOUR_API_KEY 为你的 API Key + curl_easy_setopt(client, CURLOPT_HTTPHEADER, headers); + + // 执行 CURL 请求 + err = curl_easy_perform(client); + fclose(memstream); // 关闭内存流 + + if (err != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() 失败: %s\n", curl_easy_strerror(err)); + } else { + // 打印天气信息 + print_weather_info(response, size); + } + + // 清理资源 + curl_slist_free_all(headers); + curl_easy_cleanup(client); + free(response); + curl_global_cleanup(); + + return 0; +} -- Gitee