Initial import of hack code from CCCamp23
Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
This commit is contained in:
commit
c3799b46d9
|
@ -0,0 +1,2 @@
|
|||
*.o
|
||||
main
|
|
@ -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
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue