recieves serial audio from esp32 and pipes them into pulseaudio for other apps to enjoy
commit
a58f026d35
Binary file not shown.
|
|
@ -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()
|
||||||
|
|
||||||
|
|
@ -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")
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,105 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,56 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <termios.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,109 @@
|
||||||
|
// serial_to_wav.c
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue