From 5abbeb5dcdba97fef51e116987e5dd25024be784 Mon Sep 17 00:00:00 2001 From: talksik Date: Sun, 13 Jul 2025 11:52:09 -0700 Subject: [PATCH] capture audio with alsa --- ai-conversation/Makefile | 14 ++++ ai-conversation/README.md | 12 ++++ ai-conversation/audio_capture | Bin 0 -> 24944 bytes ai-conversation/audio_capture.c | 123 ++++++++++++++++++++++++++++++++ ai-conversation/main.c | 5 ++ 5 files changed, 154 insertions(+) create mode 100644 ai-conversation/Makefile create mode 100644 ai-conversation/README.md create mode 100755 ai-conversation/audio_capture create mode 100644 ai-conversation/audio_capture.c create mode 100644 ai-conversation/main.c diff --git a/ai-conversation/Makefile b/ai-conversation/Makefile new file mode 100644 index 0000000..1f4f296 --- /dev/null +++ b/ai-conversation/Makefile @@ -0,0 +1,14 @@ +CC = gcc +CFLAGS = -Wall -g +TARGET = audio_capture +SRC = audio_capture.c +LIBS = -lavdevice -lavformat -lavcodec -lavutil -lasound + +$(TARGET): $(SRC) + $(CC) $(CFLAGS) -o $(TARGET) $(SRC) $(LIBS) + +clean: + rm -f *.o $(TARGET) + +install-deps: + sudo apt install build-essential libavformat-dev libavdevice-dev libavcodec-dev libavutil-dev libasound2-dev diff --git a/ai-conversation/README.md b/ai-conversation/README.md new file mode 100644 index 0000000..29daba2 --- /dev/null +++ b/ai-conversation/README.md @@ -0,0 +1,12 @@ +# Conversation +This proof of concept is for implementing conversational loop with AI at a low level. + +- [x] Capture audio +- [ ] Use deepgram for live transcription +- [ ] Detect final speech/turn detection +- [ ] AI answer -> text +- [ ] Text -> generated voice with deepgram +- [ ] Playback on client side + +## Requirements +Platform: the project is meant for Linux, specifically Ubuntu desktop. diff --git a/ai-conversation/audio_capture b/ai-conversation/audio_capture new file mode 100755 index 0000000000000000000000000000000000000000..99d283e0d4c5fc496151a473d5710dd3b8549066 GIT binary patch literal 24944 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 +#include +#include +#include +#include + +// NOTE: find this via `arecord -l` +#define ALSA_MIC_HARDWARE_DEVICE "hw:3" + +void print_pcm_state(snd_pcm_t *pcm_handle) { + snd_pcm_state_t state = snd_pcm_state(pcm_handle); + const char *state_name = snd_pcm_state_name(state); + printf("State of pcm handle: %s\n", state_name); +} + +int main() { + snd_pcm_t *pcm_handle = NULL; + + int err; + err = snd_pcm_open(&pcm_handle, ALSA_MIC_HARDWARE_DEVICE, SND_PCM_STREAM_CAPTURE, 0); + if (err < 0) { + printf("unable to open pcm device"); + return -1; + } + print_pcm_state(pcm_handle); + + const char *name = snd_pcm_name(pcm_handle); + printf("found this device: %s\n", name); + + snd_pcm_info_t *pcm_info; + snd_pcm_info_malloc(&pcm_info); + err = snd_pcm_info(pcm_handle, pcm_info); + if (err < 0) { + snd_pcm_close(pcm_handle); + printf("unable to get info of pcm device"); + return -1; + } + name = snd_pcm_info_get_name(pcm_info); + if (err < 0) { + printf("unable to get card info"); + snd_pcm_close(pcm_handle); + snd_pcm_info_free(pcm_info); + return -1; + } + printf("got pcm name: %s \n", name); + + print_pcm_state(pcm_handle); + snd_pcm_info_free(pcm_info); + + snd_pcm_hw_params_t *params; + err = snd_pcm_hw_params_malloc(¶ms); + if (err < 0) { + fprintf(stderr, "unable to mallow hw params"); + return -1; + } + err = snd_pcm_hw_params_any(pcm_handle, params); + if (err < 0) { + fprintf(stderr, "unable to find ranges for hw device"); + snd_pcm_close(pcm_handle); + snd_pcm_hw_params_free(params); + return -1; + } + + uint period_size = 1024; + uint buffer_size = 4096; + uint channel_count = 1; + uint bytes_per_sample = 2; // FORMAT_S16_LE + uint samples_per_second = 48000; + snd_pcm_hw_params_set_access(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_channels(pcm_handle, params, channel_count); + snd_pcm_hw_params_set_buffer_size(pcm_handle, params, buffer_size); + snd_pcm_hw_params_set_format(pcm_handle, params, SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_rate(pcm_handle, params, samples_per_second, 0); + snd_pcm_hw_params_set_periods(pcm_handle, params, period_size, 0); + + err = snd_pcm_hw_params(pcm_handle, params); + if (err < 0) { + fprintf(stderr, "unable to prepare pcm\n"); + snd_pcm_close(pcm_handle); + snd_pcm_hw_params_free(params); + return -1; + } + + print_pcm_state(pcm_handle); + + err = snd_pcm_start(pcm_handle); + if (err < 0) { + fprintf(stderr, "unable to start pcm"); + snd_pcm_close(pcm_handle); + snd_pcm_hw_params_free(params); + return -1; + } + + print_pcm_state(pcm_handle); + + const uint seconds_to_capture = 5; + const uint periods_to_read = (samples_per_second * seconds_to_capture) / period_size; + char *buf = malloc(period_size * channel_count * bytes_per_sample); + uint periods_read = 0; + /* Read one period at a time from the hardware buffer + * We set the hardware buffer to read 4096 frames. + * The period size is 1024 frames. + * Each period is 2048 BYTES because each frame is 2 bytes. + * Each frame is 2 bytes because of 1 channel, and 16 bit format + */ + while (snd_pcm_readi(pcm_handle, (void *)buf, period_size) > 0 && periods_read < periods_to_read) { + printf("======READING FRAMES========\n"); + + // Because each frame is 2 bytes (format/bit rate), cast to int16 + int16_t *samples = (int16_t *)buf; + for (int i = 0; i < period_size; i++) { + printf("%d ", samples[i]); + } + printf("\n"); + periods_read++; + } + + free((void *)buf); + snd_pcm_close(pcm_handle); + snd_pcm_hw_params_free(params); + + return 0; +} diff --git a/ai-conversation/main.c b/ai-conversation/main.c new file mode 100644 index 0000000..6c4d7f9 --- /dev/null +++ b/ai-conversation/main.c @@ -0,0 +1,5 @@ +#include + +int main() { + printf("hello world \n"); +}