commit a58f026d35dbc9c481b1fa4bf7453d6a665b9b51 Author: jake Date: Sat Nov 15 09:51:40 2025 +0000 recieves serial audio from esp32 and pipes them into pulseaudio for other apps to enjoy diff --git a/capture.wav b/capture.wav new file mode 100644 index 0000000..c98a3d2 Binary files /dev/null and b/capture.wav differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..8b194bc --- /dev/null +++ b/main.py @@ -0,0 +1,37 @@ +import serial +import pyaudio +import numpy as np + +SERIAL_PORT = '/dev/ttyACM0' # Change this to your serial port +BAUD_RATE = 1500000 +CHUNK_SIZE = 256 # Number of frames in each block + +p = pyaudio.PyAudio() + +stream = p.open(format=pyaudio.paInt16, + channels=2, + rate=16000, + output=True) + +ser = serial.Serial(SERIAL_PORT, BAUD_RATE) + +print("Streaming audio...") + +try: + while True: + data = ser.read(CHUNK_SIZE * 4) # 2 channels × 2 bytes + if not data: + break + + audio_data = np.frombuffer(data, dtype=np.int16) + stream.write(audio_data.tobytes()) + +except KeyboardInterrupt: + print("Stopping audio stream...") + +finally: + stream.stop_stream() + stream.close() + p.terminate() + ser.close() + diff --git a/serial_audio.py b/serial_audio.py new file mode 100644 index 0000000..b95788b --- /dev/null +++ b/serial_audio.py @@ -0,0 +1,38 @@ +import serial +import numpy as np +import wave +import time + +SERIAL_PORT = '/dev/ttyACM0' # Adjust if needed +BAUD_RATE = 1500000 +CHUNK_SIZE = 256 # frames per block +RATE = 16000 # sample rate +CHANNELS = 2 # stereo + +# open serial +ser = serial.Serial(SERIAL_PORT, BAUD_RATE) + +print("Recording 3 seconds of audio...") + +frames = [] +start = time.time() + +try: + while time.time() - start < 3.0: + # 2 channels × 2 bytes per sample = 4 bytes per frame + data = ser.read(CHUNK_SIZE * 4) + if not data: + break + frames.append(data) + +finally: + ser.close() + +# write to wav +with wave.open("capture.wav", "wb") as wf: + wf.setnchannels(CHANNELS) + wf.setsampwidth(2) # 16‑bit samples = 2 bytes + wf.setframerate(RATE) + wf.writeframes(b"".join(frames)) + +print("Saved capture.wav") diff --git a/serial_capture b/serial_capture new file mode 100755 index 0000000..eba1306 Binary files /dev/null and b/serial_capture differ diff --git a/serial_capture.c b/serial_capture.c new file mode 100644 index 0000000..7e91e57 --- /dev/null +++ b/serial_capture.c @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SERIAL_PORT "/dev/ttyACM1" +#define BAUD_RATE B1500000 +#define RATE 16000 +#define CHANNELS 2 +#define SAMPLE_WIDTH 2 // 16-bit +#define CHUNK_SIZE 256 // frames per block +#define DURATION 3 // seconds + +// Write a simple WAV header +void write_wav_header(FILE *f, int data_size) { + int byte_rate = RATE * CHANNELS * SAMPLE_WIDTH; + int block_align = CHANNELS * SAMPLE_WIDTH; + + // RIFF header + fwrite("RIFF", 1, 4, f); + int chunk_size = 36 + data_size; + fwrite(&chunk_size, 4, 1, f); + fwrite("WAVE", 1, 4, f); + + // fmt subchunk + fwrite("fmt ", 1, 4, f); + int subchunk1_size = 16; + fwrite(&subchunk1_size, 4, 1, f); + short audio_format = 1; // PCM + fwrite(&audio_format, 2, 1, f); + short num_channels = CHANNELS; + fwrite(&num_channels, 2, 1, f); + int sample_rate = RATE; + fwrite(&sample_rate, 4, 1, f); + fwrite(&byte_rate, 4, 1, f); + fwrite(&block_align, 2, 1, f); + short bits_per_sample = SAMPLE_WIDTH * 8; + fwrite(&bits_per_sample, 2, 1, f); + + // data subchunk + fwrite("data", 1, 4, f); + fwrite(&data_size, 4, 1, f); +} + +int main() { + // Open serial port + int fd = open(SERIAL_PORT, O_RDONLY | O_NOCTTY); + if (fd < 0) { + perror("open"); + return 1; + } + + // Configure serial + struct termios tty; + memset(&tty, 0, sizeof tty); + if (tcgetattr(fd, &tty) != 0) { + perror("tcgetattr"); + return 1; + } + cfsetospeed(&tty, BAUD_RATE); + cfsetispeed(&tty, BAUD_RATE); + tty.c_cflag |= (CLOCAL | CREAD); + tty.c_cflag &= ~CSIZE; + tty.c_cflag |= CS8; + tty.c_cflag &= ~PARENB; + tty.c_cflag &= ~CSTOPB; + tcsetattr(fd, TCSANOW, &tty); + + FILE *out = fopen("capture.wav", "wb"); + if (!out) { + perror("fopen"); + return 1; + } + + // Reserve space for header + fseek(out, 44, SEEK_SET); + + int total_bytes = 0; + char buf[CHUNK_SIZE * CHANNELS * SAMPLE_WIDTH]; + time_t start = time(NULL); + + while (time(NULL) - start < DURATION) { + int n = read(fd, buf, sizeof(buf)); + if (n > 0) { + fwrite(buf, 1, n, out); + total_bytes += n; + } + } + + // Write header at beginning + fseek(out, 0, SEEK_SET); + write_wav_header(out, total_bytes); + + fclose(out); + close(fd); + + printf("Saved capture.wav (%d bytes of audio)\n", total_bytes); + return 0; +} diff --git a/serial_to_stdout b/serial_to_stdout new file mode 100755 index 0000000..197bd47 Binary files /dev/null and b/serial_to_stdout differ diff --git a/serial_to_stdout.c b/serial_to_stdout.c new file mode 100644 index 0000000..10cd9de --- /dev/null +++ b/serial_to_stdout.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_PORT "/dev/ttyESP32_A" +#define DEFAULT_BAUD B1500000 + +int open_serial(const char *path, speed_t baud) { + int fd = open(path, O_RDONLY | O_NOCTTY); + if (fd < 0) { perror("open"); return -1; } + + struct termios tio; + if (tcgetattr(fd, &tio) != 0) { perror("tcgetattr"); close(fd); return -1; } + + cfsetispeed(&tio, baud); + cfsetospeed(&tio, baud); + + // Raw mode + tio.c_iflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | IXON | IXOFF | IXANY | + PARMRK | INPCK | ISTRIP); + tio.c_oflag &= ~(OPOST); + tio.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG); + tio.c_cflag &= ~(CSIZE | PARENB | CSTOPB | CRTSCTS); + tio.c_cflag |= (CS8 | CLOCAL | CREAD); + + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + + if (tcsetattr(fd, TCSANOW, &tio) != 0) { perror("tcsetattr"); close(fd); return -1; } + + return fd; +} + +int main(int argc, char *argv[]) { + const char *port = (argc > 1) ? argv[1] : DEFAULT_PORT; + speed_t baud = DEFAULT_BAUD; + + int fd = open_serial(port, baud); + if (fd < 0) return 1; + + uint8_t buf[8192]; + while (1) { + ssize_t n = read(fd, buf, sizeof(buf)); + if (n > 0) { + fwrite(buf, 1, n, stdout); + fflush(stdout); + } + } + + close(fd); + return 0; +} diff --git a/serial_to_wav b/serial_to_wav new file mode 100755 index 0000000..1574b3c Binary files /dev/null and b/serial_to_wav differ diff --git a/serial_to_wav.c b/serial_to_wav.c new file mode 100644 index 0000000..9c07153 --- /dev/null +++ b/serial_to_wav.c @@ -0,0 +1,109 @@ +// serial_to_wav.c +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SERIAL_PORT "/dev/ttyESP32_A" +#define BAUD_RATE B1500000 +#define RATE 16000 +#define CHANNELS 2 +#define BPS 16 + +static volatile sig_atomic_t stop_flag = 0; +void on_sigint(int sig) { (void)sig; stop_flag = 1; } + +// Write RIFF/WAVE header placeholders; sizes will be patched on exit +void write_wav_header(FILE *f, uint32_t data_bytes) { + uint32_t byte_rate = RATE * CHANNELS * (BPS/8); + uint16_t block_align = CHANNELS * (BPS/8); + uint32_t riff_size = 36 + data_bytes; + + fseek(f, 0, SEEK_SET); + fwrite("RIFF", 1, 4, f); + fwrite(&riff_size, 4, 1, f); + fwrite("WAVE", 1, 4, f); + + fwrite("fmt ", 1, 4, f); + uint32_t fmt_size = 16; + fwrite(&fmt_size, 4, 1, f); + uint16_t audio_fmt = 1; + fwrite(&audio_fmt, 2, 1, f); + uint16_t ch = CHANNELS; + fwrite(&ch, 2, 1, f); + uint32_t sr = RATE; + fwrite(&sr, 4, 1, f); + fwrite(&byte_rate, 4, 1, f); + fwrite(&block_align, 2, 1, f); + uint16_t bits = BPS; + fwrite(&bits, 2, 1, f); + + fwrite("data", 1, 4, f); + fwrite(&data_bytes, 4, 1, f); +} + +// Configure serial port for raw 8N1, no flow control, blocking reads +int open_serial(const char *path) { + int fd = open(path, O_RDONLY | O_NOCTTY); + if (fd < 0) { perror("open"); return -1; } + + struct termios tio; + if (tcgetattr(fd, &tio) != 0) { perror("tcgetattr"); close(fd); return -1; } + + cfsetispeed(&tio, BAUD_RATE); + cfsetospeed(&tio, BAUD_RATE); + + // Raw mode: disable all translations + tio.c_iflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | IXON | IXOFF | IXANY | PARMRK | INPCK | ISTRIP); + tio.c_oflag &= ~(OPOST); + tio.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG); + tio.c_cflag &= ~(CSIZE | PARENB | CSTOPB | CRTSCTS); + tio.c_cflag |= (CS8 | CLOCAL | CREAD); + + // Blocking read: return when at least N bytes available + tio.c_cc[VMIN] = 1; // at least 1 byte + tio.c_cc[VTIME] = 0; // no timeout + + if (tcsetattr(fd, TCSANOW, &tio) != 0) { perror("tcsetattr"); close(fd); return -1; } + + return fd; +} + +int main() { + signal(SIGINT, on_sigint); + + int fd = open_serial(SERIAL_PORT); + if (fd < 0) return 1; + + FILE *out = fopen("capture.wav", "wb+"); + if (!out) { perror("fopen"); close(fd); return 1; } + + // Reserve header space + uint32_t data_bytes = 0; + fseek(out, 44, SEEK_SET); + + // Capture loop + uint8_t buf[8192]; + while (!stop_flag) { + ssize_t n = read(fd, buf, sizeof(buf)); + if (n > 0) { + fwrite(buf, 1, (size_t)n, out); + data_bytes += (uint32_t)n; + } + } + + // Finalize header + write_wav_header(out, data_bytes); + fflush(out); + fclose(out); + close(fd); + + fprintf(stderr, "Saved capture.wav with %u bytes of audio\n", data_bytes); + return 0; +} diff --git a/start_esp32_audio.sh b/start_esp32_audio.sh new file mode 100755 index 0000000..d442fdf --- /dev/null +++ b/start_esp32_audio.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Start ESP32 audio capture into PulseAudio + +# 1. Ensure null sink exists at 16kHz +pactl unload-module module-null-sink 2>/dev/null +pactl load-module module-null-sink sink_name=esp32 rate=16000 channels=2 format=s16le + +# 2. Run serial capture and feed into sink +/home/littlesophia/serial_audio_catcher/serial_to_stdout /dev/ttyESP32_A | \ +pacat --raw --rate=16000 --channels=2 --format=s16le --device=esp32 diff --git a/test.wav b/test.wav new file mode 100644 index 0000000..f7cc9fb Binary files /dev/null and b/test.wav differ