Initial import of hack code from CCCamp23

Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
This commit is contained in:
Sylvain Munaut 2023-08-23 17:36:09 +02:00
commit c3799b46d9
3 changed files with 432 additions and 0 deletions

2
hack/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.o
main

14
hack/Makefile Normal file
View File

@ -0,0 +1,14 @@
CC=gcc
CFLAGS=-Wall -Wno-unused
LDLIBS=-lm -ltiff -ljpeg
CFLAGS += `pkg-config --cflags libpulse`
LDLIBS += `pkg-config --libs libpulse`
CFLAGS += `pkg-config --cflags spandsp`
LDLIBS += `pkg-config --libs spandsp`
main: main.o
clean:
rm -f *.o main

416
hack/main.c Normal file
View File

@ -0,0 +1,416 @@
#include <stdio.h>
#include <errno.h>
#include <pulse/pulseaudio.h>
#include <spandsp.h>
struct app_state {
/* pulse audio */
struct {
pa_mainloop *ml;
pa_mainloop_api *mlapi;
pa_context *ctx;
pa_stream *tx;
pa_stream *rx;
int ready;
} pa;
/* spandsp */
struct {
fax_state_t *fax;
t30_state_t *t30;
} spandsp;
/* dialling */
struct {
char *num;
int count;
float phase[2];
} dial;
/* state */
enum {
STATE_DIAL = 0,
STATE_FAX,
} state;
} g_app;
/* ------------------------------------------------------------------------ */
/* DTMF */
/* ------------------------------------------------------------------------ */
struct dtmf_tone {
char code;
int freq[2];
};
static const struct dtmf_tone dtmf_tones_data[] = {
{'1', {1209, 697}},
{'2', {1336, 697}},
{'3', {1477, 697}},
{'A', {1633, 697}},
{'4', {1209, 770}},
{'5', {1336, 770}},
{'6', {1477, 770}},
{'B', {1633, 770}},
{'7', {1209, 852}},
{'8', {1336, 852}},
{'9', {1477, 852}},
{'C', {1633, 852}},
{'*', {1209, 941}},
{'0', {1336, 941}},
{'#', {1477, 941}},
{'D', {1633, 941}},
{}
};
static void
dtmf_fill(float *phase_state, char code, int16_t *out, int nsamps)
{
const struct dtmf_tone *tone = NULL;
float phase_cur, phase_inc;
int i, j;
/* Find code */
for (i=0; dtmf_tones_data[i].code; i++)
if (dtmf_tones_data[i].code == code) {
tone = &dtmf_tones_data[i];
break;
}
if (!tone)
return;
/* Clear buffer */
memset(out, 0x00, sizeof(int16_t) * nsamps);
/* Add tones */
for (j=0; j<2; j++)
{
phase_cur = phase_state[j];
phase_inc = (2.0f * M_PI) / (8000.0f / tone->freq[j]);
for (i=0; i<nsamps; i++) {
out[i] += (int16_t)(sinf(phase_cur) * 8192);
phase_cur += phase_inc;
}
phase_state[j] = phase_cur;
}
}
static int
dtmf_dial(struct app_state *as, int16_t *out, int nsamps)
{
int ofs = 0;
int num_digit;
int cur_digit;
int cur_samp;
/* Current progress */
num_digit = strlen(as->dial.num);
cur_digit = as->dial.count >> 12;
cur_samp = as->dial.count & 4095;
while (ofs < nsamps)
{
bool clear;
int l;
/* Handle end */
if (cur_digit > num_digit) {
memset(&out[ofs], 0x00, nsamps - ofs);
as->state = STATE_FAX;
break;
}
/* Are we at the pause between digits */
if (cur_samp >= 3084) {
l = 4096 - cur_samp;
clear = true;
} else {
l = 3084 - cur_samp;
clear = false;
}
/* Are we at the end ? */
if (cur_digit == num_digit) {
clear = true;
}
/* Limit to buffer */
if (l > (nsamps - ofs))
l = nsamps - ofs;
/* Fill or clear */
if (clear) {
memset(&out[ofs], 0x00, l * sizeof(int16_t));
} else {
dtmf_fill(as->dial.phase, as->dial.num[cur_digit], &out[ofs], l);
}
/* Progress */
ofs += l;
cur_samp += l;
if (cur_samp >= 4096) {
cur_samp -= 4096;
cur_digit += 1;
}
}
/* Save progress */
as->dial.count = (cur_digit << 12) | cur_samp;
return nsamps;
}
/* ------------------------------------------------------------------------ */
/* Pulse audio */
/* ------------------------------------------------------------------------ */
static void
pa_rx_data_cb(pa_stream *p, size_t nbytes, void *userdata)
{
struct app_state *as = userdata;
/* Peek chunks until there are none */
while (1) {
const int16_t *data_buf;
size_t data_len;
int nsamps;
/* Get data */
pa_stream_peek(p, (const void **)&data_buf, &data_len);
if (!data_len)
break;
nsamps = nbytes / sizeof(int16_t);
/* Feed to fax if in that state */
if (as->state == STATE_FAX) {
/* FIXME ... data_buf is modified ... */
fax_rx(as->spandsp.fax, data_buf, nsamps);
}
/* Drop chunk */
pa_stream_drop(p);
}
}
static void
pa_rx_overflow_cb(pa_stream *p, void *userdata)
{
struct app_state *as = userdata;
printf("RX Overflow\n");
}
static void
pa_tx_data_cb(pa_stream *p, size_t nbytes, void *userdata)
{
struct app_state *as = userdata;
int nsamps = nbytes / sizeof(int16_t);
int16_t buf[nbytes];
/* Dial or fax ? */
if (as->state == STATE_DIAL) {
nsamps = dtmf_dial(as, buf, nsamps);
} else if (as->state == STATE_FAX) {
nsamps = fax_tx(as->spandsp.fax, buf, nsamps);
}
/* Feed to PA */
nbytes = nsamps * sizeof(int16_t);
pa_stream_write(p, buf, nbytes, NULL, 0LL, PA_SEEK_RELATIVE);
}
static void
pa_tx_underflow_cb(pa_stream *p, void *userdata)
{
struct app_state *as = userdata;
printf("TX Underflow\n");
}
static void
pa_state_cb(pa_context *c, void *userdata)
{
pa_context_state_t state;
int *ready = userdata;
state = pa_context_get_state(c);
switch (state) {
case PA_CONTEXT_UNCONNECTED:
case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
default:
break;
case PA_CONTEXT_FAILED:
case PA_CONTEXT_TERMINATED:
*ready = 2;
break;
case PA_CONTEXT_READY:
*ready = 1;
break;
}
}
static void
pa_release(struct app_state *as)
{
/* FIXME */
}
static int
pa_init(struct app_state *as)
{
int rv;
/* Stream configs */
static const pa_sample_spec ss = {
.format = PA_SAMPLE_S16NE,
.rate = 8000,
.channels = 1
};
const int latency = 150 * 1000;
const int minreq = 50 * 1000;
const pa_buffer_attr bufattr = {
.fragsize = (uint32_t) -1,
.maxlength = pa_usec_to_bytes(latency, &ss),
.minreq = pa_usec_to_bytes(minreq, &ss),
.prebuf = (uint32_t) -1,
.tlength = pa_usec_to_bytes(latency, &ss),
};
/* Temporary buffer */
/* Create main loop and context */
as->pa.ml = pa_mainloop_new();
as->pa.mlapi = pa_mainloop_get_api(as->pa.ml);
/* Create context */
as->pa.ctx = pa_context_new(as->pa.mlapi, "amr_fax");
pa_context_set_state_callback(as->pa.ctx, pa_state_cb, &as->pa.ready);
/* Connect */
pa_context_connect(as->pa.ctx, NULL, 0, NULL);
/* Wait for PA start */
while (as->pa.ready == 0)
pa_mainloop_iterate(as->pa.ml, 1, NULL);
if (as->pa.ready == 2) {
pa_release(as);
return -EIO;
}
/* Create RX stream */
as->pa.rx = pa_stream_new(as->pa.ctx, "RX", &ss, NULL);
if (!as->pa.rx) {
fprintf(stderr, "pa_stream_new failed\n");
pa_release(as);
return -EBUSY;
}
pa_stream_set_read_callback(as->pa.rx, pa_rx_data_cb, as);
pa_stream_set_overflow_callback(as->pa.rx, pa_rx_overflow_cb, as);
rv = pa_stream_connect_record(as->pa.rx,
"alsa_input.usb-osmocom_AMR_modem_interface_e46848d71f441d2d-01.mono-fallback",
&bufattr,
PA_STREAM_INTERPOLATE_TIMING |
PA_STREAM_ADJUST_LATENCY |
PA_STREAM_AUTO_TIMING_UPDATE
);
/* Create TX stream */
as->pa.tx = pa_stream_new(as->pa.ctx, "TX", &ss, NULL);
if (!as->pa.tx) {
fprintf(stderr, "pa_stream_new failed\n");
pa_release(as);
return -EBUSY;
}
pa_stream_set_write_callback(as->pa.tx, pa_tx_data_cb, as);
pa_stream_set_underflow_callback(as->pa.tx, pa_tx_underflow_cb, as);
rv = pa_stream_connect_playback(as->pa.tx,
"alsa_output.usb-osmocom_AMR_modem_interface_e46848d71f441d2d-01.mono-fallback",
&bufattr,
PA_STREAM_INTERPOLATE_TIMING |
PA_STREAM_ADJUST_LATENCY |
PA_STREAM_AUTO_TIMING_UPDATE,
NULL,
NULL
);
/* Done */
return 0;
}
static void
pa_run(struct app_state *as)
{
pa_mainloop_run(as->pa.ml, NULL);
}
/* ------------------------------------------------------------------------ */
/* Span DSP */
/* ------------------------------------------------------------------------ */
static int
spandsp_init(struct app_state *as)
{
as->spandsp.fax = fax_init(NULL, true);
as->spandsp.t30 = fax_get_t30_state(as->spandsp.fax);
fax_set_transmit_on_idle(as->spandsp.fax, 1);
span_log_set_level(
t30_get_logging_state(as->spandsp.t30),
SPAN_LOG_DEBUG
);
t30_set_tx_sender_ident(as->spandsp.t30, "tnt (2460)");
// t30_set_rx_file(as->spandsp.t30, "fax_rx.tiff", -1);
// t30_set_tx_file(as->spandsp.t30, "fax_tx.tiff", -1, -1);
t30_set_tx_file(as->spandsp.t30, "/tmp/fax/mydocument.tif", -1, -1);
return 0;
}
static void
spandsp_release(struct app_state *as)
{
fax_free(as->spandsp.fax);
}
int main(int argc, char *argv[])
{
g_app.dial.num = "9107";
spandsp_init(&g_app);
pa_init(&g_app);
while (1) {
pa_run(&g_app);
}
pa_release(&g_app);
spandsp_release(&g_app);
return 0;
}