mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-06-03 11:12:27 +00:00
Write network audio programs
This commit is contained in:
parent
1bfb348403
commit
ce2fbf9325
8 changed files with 366 additions and 4 deletions
43
dsp/prog/BUILD.mk
Normal file
43
dsp/prog/BUILD.mk
Normal file
|
@ -0,0 +1,43 @@
|
|||
#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐
|
||||
#── vi: set noet ft=make ts=8 sw=8 fenc=utf-8 :vi ────────────────────┘
|
||||
|
||||
PKGS += DSP_PROG
|
||||
|
||||
DSP_PROG_FILES := $(wildcard dsp/prog/*)
|
||||
DSP_PROG_HDRS = $(filter %.h,$(DSP_PROG_FILES))
|
||||
DSP_PROG_SRCS = $(filter %.c,$(DSP_PROG_FILES))
|
||||
DSP_PROG_OBJS = $(DSP_PROG_SRCS:%.c=o/$(MODE)/%.o)
|
||||
DSP_PROG_COMS = $(DSP_PROG_SRCS:%.c=o/$(MODE)/%)
|
||||
DSP_PROG_BINS = $(DSP_PROG_COMS) $(DSP_PROG_COMS:%=%.dbg)
|
||||
|
||||
DSP_PROG_DIRECTDEPS = \
|
||||
DSP_AUDIO \
|
||||
LIBC_CALLS \
|
||||
LIBC_INTRIN \
|
||||
LIBC_NEXGEN32E \
|
||||
LIBC_RUNTIME \
|
||||
LIBC_SOCK \
|
||||
LIBC_STDIO \
|
||||
LIBC_SYSV \
|
||||
LIBC_TINYMATH \
|
||||
THIRD_PARTY_MUSL \
|
||||
|
||||
DSP_PROG_DEPS := \
|
||||
$(call uniq,$(foreach x,$(DSP_PROG_DIRECTDEPS),$($(x))))
|
||||
|
||||
o/$(MODE)/dsp/prog/prog.pkg: \
|
||||
$(DSP_PROG_OBJS) \
|
||||
$(foreach x,$(DSP_PROG_DIRECTDEPS),$($(x)_A).pkg)
|
||||
|
||||
o/$(MODE)/dsp/prog/%.dbg: \
|
||||
$(DSP_PROG_DEPS) \
|
||||
o/$(MODE)/dsp/prog/prog.pkg \
|
||||
o/$(MODE)/dsp/prog/%.o \
|
||||
$(CRT) \
|
||||
$(APE_NO_MODIFY_SELF)
|
||||
@$(APELINK)
|
||||
|
||||
$(DSP_PROG_OBJS): dsp/prog/BUILD.mk
|
||||
|
||||
.PHONY: o/$(MODE)/dsp/prog
|
||||
o/$(MODE)/dsp/prog: $(DSP_PROG_BINS)
|
41
dsp/prog/loudness.h
Normal file
41
dsp/prog/loudness.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
#ifndef COSMOPOLITAN_DSP_PROG_LOUDNESS_H_
|
||||
#define COSMOPOLITAN_DSP_PROG_LOUDNESS_H_
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define MIN_DECIBEL -60
|
||||
#define MAX_DECIBEL 0
|
||||
|
||||
// computes root of mean squares
|
||||
static double rms(float *p, int n) {
|
||||
double s = 0;
|
||||
for (int i = 0; i < n; ++i)
|
||||
s += p[i] * p[i];
|
||||
return sqrt(s / n);
|
||||
}
|
||||
|
||||
// converts rms to decibel
|
||||
static double rms_to_db(double rms) {
|
||||
double db = 20 * log10(rms);
|
||||
db = fmin(db, MAX_DECIBEL);
|
||||
db = fmax(db, MIN_DECIBEL);
|
||||
return db;
|
||||
}
|
||||
|
||||
// char meter[21];
|
||||
// format_decibel_meter(meter, 20, rms_to_db(rms(samps, count)))
|
||||
static char *format_decibel_meter(char *meter, int width, double db) {
|
||||
double range = MAX_DECIBEL - MIN_DECIBEL;
|
||||
int filled = (db - MIN_DECIBEL) / range * width;
|
||||
for (int i = 0; i < width; ++i) {
|
||||
if (i < filled) {
|
||||
meter[i] = '=';
|
||||
} else {
|
||||
meter[i] = ' ';
|
||||
}
|
||||
}
|
||||
meter[width] = 0;
|
||||
return meter;
|
||||
}
|
||||
|
||||
#endif /* COSMOPOLITAN_DSP_PROG_LOUDNESS_H_ */
|
127
dsp/prog/recvaudio.c
Normal file
127
dsp/prog/recvaudio.c
Normal file
|
@ -0,0 +1,127 @@
|
|||
#if 0
|
||||
/*─────────────────────────────────────────────────────────────────╗
|
||||
│ To the extent possible under law, Justine Tunney has waived │
|
||||
│ all copyright and related or neighboring rights to this file, │
|
||||
│ as it is written in the following disclaimers: │
|
||||
│ • http://unlicense.org/ │
|
||||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <cosmoaudio.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <netinet/in.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include "loudness.h"
|
||||
|
||||
/**
|
||||
* @fileoverview plays audio from remote computer on speaker
|
||||
* @see dsp/prog/sendaudio.c
|
||||
*/
|
||||
|
||||
#define SAMPLING_RATE 44100
|
||||
#define FRAMES_PER_SECOND 60
|
||||
#define DEBUG_LOG 0
|
||||
#define PORT 9834
|
||||
|
||||
#define CHUNK_FRAMES (SAMPLING_RATE / FRAMES_PER_SECOND)
|
||||
|
||||
static_assert(CHUNK_FRAMES * sizeof(short) < 1472,
|
||||
"audio chunks won't fit in udp ethernet packet");
|
||||
|
||||
sig_atomic_t g_done;
|
||||
|
||||
void onsig(int sig) {
|
||||
g_done = 1;
|
||||
}
|
||||
|
||||
short toshort(float x) {
|
||||
return fmaxf(-1, fminf(1, x)) * 32767;
|
||||
}
|
||||
|
||||
float tofloat(short x) {
|
||||
return x / 32768.f;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
|
||||
// listen on udp port for audio
|
||||
int server;
|
||||
if ((server = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
|
||||
perror("socket");
|
||||
return 3;
|
||||
}
|
||||
struct sockaddr_in addr = {.sin_family = AF_INET, .sin_port = htons(PORT)};
|
||||
if (bind(server, (struct sockaddr*)&addr, sizeof(addr))) {
|
||||
perror("bind");
|
||||
return 4;
|
||||
}
|
||||
|
||||
// setup signals
|
||||
struct sigaction sa;
|
||||
sa.sa_flags = 0;
|
||||
sa.sa_handler = onsig;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sigaction(SIGINT, &sa, 0);
|
||||
|
||||
// configure cosmo audio
|
||||
struct CosmoAudioOpenOptions cao = {0};
|
||||
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
|
||||
cao.deviceType = kCosmoAudioDeviceTypePlayback;
|
||||
cao.sampleRate = SAMPLING_RATE;
|
||||
cao.bufferFrames = CHUNK_FRAMES * 2;
|
||||
cao.debugLog = DEBUG_LOG;
|
||||
cao.channels = 1;
|
||||
|
||||
// connect to microphone and speaker
|
||||
int status;
|
||||
struct CosmoAudio* ca;
|
||||
status = cosmoaudio_open(&ca, &cao);
|
||||
if (status != COSMOAUDIO_SUCCESS) {
|
||||
fprintf(stderr, "failed to open audio: %d\n", status);
|
||||
return 5;
|
||||
}
|
||||
|
||||
while (!g_done) {
|
||||
// read from network
|
||||
ssize_t got;
|
||||
short buf16[CHUNK_FRAMES];
|
||||
if ((got = read(server, buf16, CHUNK_FRAMES * sizeof(short))) == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
perror("read");
|
||||
return 7;
|
||||
}
|
||||
if (got != CHUNK_FRAMES * sizeof(short)) {
|
||||
fprintf(stderr, "warning: got partial audio frame\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// write to speaker
|
||||
float buf32[CHUNK_FRAMES];
|
||||
for (int i = 0; i < CHUNK_FRAMES; ++i)
|
||||
buf32[i] = tofloat(buf16[i]);
|
||||
cosmoaudio_poll(ca, 0, (int[]){CHUNK_FRAMES});
|
||||
cosmoaudio_write(ca, buf32, CHUNK_FRAMES);
|
||||
|
||||
// print loudness in ascii
|
||||
char meter[21];
|
||||
double db = rms_to_db(rms(buf32, CHUNK_FRAMES));
|
||||
format_decibel_meter(meter, 20, db);
|
||||
printf("\r%s| %+6.2f dB", meter, db);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// clean up resources
|
||||
cosmoaudio_flush(ca);
|
||||
cosmoaudio_close(ca);
|
||||
close(server);
|
||||
}
|
149
dsp/prog/sendaudio.c
Normal file
149
dsp/prog/sendaudio.c
Normal file
|
@ -0,0 +1,149 @@
|
|||
#if 0
|
||||
/*─────────────────────────────────────────────────────────────────╗
|
||||
│ To the extent possible under law, Justine Tunney has waived │
|
||||
│ all copyright and related or neighboring rights to this file, │
|
||||
│ as it is written in the following disclaimers: │
|
||||
│ • http://unlicense.org/ │
|
||||
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
|
||||
╚─────────────────────────────────────────────────────────────────*/
|
||||
#endif
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <cosmoaudio.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include "loudness.h"
|
||||
|
||||
/**
|
||||
* @fileoverview sends audio from microphone to remote computer
|
||||
* @see dsp/prog/recvaudio.c
|
||||
*/
|
||||
|
||||
#define SAMPLING_RATE 44100
|
||||
#define FRAMES_PER_SECOND 60
|
||||
#define DEBUG_LOG 0
|
||||
#define PORT 9834
|
||||
|
||||
#define CHUNK_FRAMES (SAMPLING_RATE / FRAMES_PER_SECOND)
|
||||
|
||||
static_assert(CHUNK_FRAMES * sizeof(short) < 1472,
|
||||
"audio chunks won't fit in udp ethernet packet");
|
||||
|
||||
sig_atomic_t g_done;
|
||||
|
||||
void onsig(int sig) {
|
||||
g_done = 1;
|
||||
}
|
||||
|
||||
short toshort(float x) {
|
||||
return fmaxf(-1, fminf(1, x)) * 32767;
|
||||
}
|
||||
|
||||
float tofloat(short x) {
|
||||
return x / 32768.f;
|
||||
}
|
||||
|
||||
uint32_t host2ip(const char* host) {
|
||||
uint32_t ip;
|
||||
if ((ip = inet_addr(host)) != -1u)
|
||||
return ip;
|
||||
int rc;
|
||||
struct addrinfo* ai = NULL;
|
||||
struct addrinfo hint = {AI_NUMERICSERV, AF_INET, SOCK_STREAM, IPPROTO_TCP};
|
||||
if ((rc = getaddrinfo(host, "0", &hint, &ai))) {
|
||||
fprintf(stderr, "%s: %s\n", host, gai_strerror(rc));
|
||||
exit(50 + rc);
|
||||
}
|
||||
ip = ntohl(((struct sockaddr_in*)ai->ai_addr)->sin_addr.s_addr);
|
||||
freeaddrinfo(ai);
|
||||
return ip;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "%s: missing host argument\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// get host argument
|
||||
const char* remote_host = argv[1];
|
||||
uint32_t ip = host2ip(remote_host);
|
||||
|
||||
// connect to server
|
||||
int client;
|
||||
if ((client = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
|
||||
perror(remote_host);
|
||||
return 3;
|
||||
}
|
||||
struct sockaddr_in addr = {.sin_family = AF_INET,
|
||||
.sin_port = htons(PORT),
|
||||
.sin_addr.s_addr = htonl(ip)};
|
||||
if (connect(client, (struct sockaddr*)&addr, sizeof(addr))) {
|
||||
perror(remote_host);
|
||||
return 4;
|
||||
}
|
||||
|
||||
// setup signals
|
||||
struct sigaction sa;
|
||||
sa.sa_flags = 0;
|
||||
sa.sa_handler = onsig;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sigaction(SIGINT, &sa, 0);
|
||||
|
||||
// configure cosmo audio
|
||||
struct CosmoAudioOpenOptions cao = {0};
|
||||
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
|
||||
cao.deviceType = kCosmoAudioDeviceTypeCapture;
|
||||
cao.sampleRate = SAMPLING_RATE;
|
||||
cao.bufferFrames = CHUNK_FRAMES * 2;
|
||||
cao.debugLog = DEBUG_LOG;
|
||||
cao.channels = 1;
|
||||
|
||||
// connect to microphone and speaker
|
||||
int status;
|
||||
struct CosmoAudio* ca;
|
||||
status = cosmoaudio_open(&ca, &cao);
|
||||
if (status != COSMOAUDIO_SUCCESS) {
|
||||
fprintf(stderr, "failed to open audio: %d\n", status);
|
||||
return 5;
|
||||
}
|
||||
|
||||
while (!g_done) {
|
||||
// read from microphone
|
||||
float buf32[CHUNK_FRAMES];
|
||||
cosmoaudio_poll(ca, (int[]){CHUNK_FRAMES}, 0);
|
||||
cosmoaudio_read(ca, buf32, CHUNK_FRAMES);
|
||||
short buf16[CHUNK_FRAMES];
|
||||
for (int i = 0; i < CHUNK_FRAMES; ++i)
|
||||
buf16[i] = toshort(buf32[i]);
|
||||
|
||||
// send to server
|
||||
if (write(client, buf16, CHUNK_FRAMES * sizeof(short)) == -1) {
|
||||
if (errno == EINTR && g_done)
|
||||
break;
|
||||
perror(remote_host);
|
||||
return 7;
|
||||
}
|
||||
|
||||
// print loudness in ascii
|
||||
char meter[21];
|
||||
double db = rms_to_db(rms(buf32, CHUNK_FRAMES));
|
||||
format_decibel_meter(meter, 20, db);
|
||||
printf("\r%s| %+6.2f dB", meter, db);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// clean up resources
|
||||
cosmoaudio_close(ca);
|
||||
close(client);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue