2 * GTA04 gsm voice routing utility
3 * Copyright (c) 2012 Radek Polak
5 * gta04-gsm-voice-routing is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * gta04-gsm-voice-routing is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with gta04-gsm-voice-routing; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 This program routes sound between GTA04 internal sound card ("default") and
23 and UMTS modem sound card ("hw:1,0")
25 The function can be written with following shell script:
27 arecord -fS16_LE | aplay -Dhw:1,0 &
28 arecord -Dhw:1,0 -fS16_LE | aplay
30 However the program is written in C for better control over the process. E.g.
31 we want to wait with routing until sound is available from UMTS.
33 We have 4 streams called r0, p1, r1, p0
35 r0 - record from hw:0,0 (default) internal sound card
36 r1 - record from hw:1,0 umts sound card
37 p0 - play on hw:0,0 (default) internal sound card
38 p1 - play on hw:1,0 umts sound card
40 All streams have rate 8000 (rate of umts sound card), 1 channel and 16bit per
41 sample (SND_PCM_FORMAT_S16_LE).
43 We set buffer_size of sound card to 1024.
44 The sound card buffer consists of 4 periods.
46 Frame has just one sample (one channel) and both sample and frame are 2 bytes
48 We always play/record one period (256 samples, 512 bytes). At rate 8000Hz if
49 we had 8000 samples it would be 1s, with 256 samples it makes 31.25ms long
50 period - this is our latency.
54 /* Use the newer ALSA API */
55 #define ALSA_PCM_NEW_HW_PARAMS_API
57 /* Define this to enable speex acoustic echo cancellation */
60 /* Define this to use walkie talkie echo reduction */
61 //#define USE_WALKIE_TALKIE_AEC
69 #include <alsa/asoundlib.h>
72 #include <speex/speex_echo.h>
75 #define ERR_PCM_OPEN_FAILED -1
76 #define ERR_HW_PARAMS_ANY -2
77 #define ERR_HW_PARAMS_SET_ACCESS -3
78 #define ERR_HW_PARAMS_SET_FORMAT -4
79 #define ERR_HW_PARAMS_SET_CHANNELS -5
80 #define ERR_HW_PARAMS_SET_RATE -6
81 #define ERR_SW_PARAMS_CURRENT -7
82 #define ERR_HW_PARAMS_SET_PERIOD_SIZE -8
83 #define ERR_HW_PARAMS_SET_BUFFER_SIZE -9
84 #define ERR_HW_PARAMS -10
85 #define ERR_BUFFER_ALLOC_FAILED -11
86 #define ERR_SW_PARAMS_SET_START_THRESHOLD -12
87 #define ERR_SW_PARAMS_SET_STOP_THRESHOLD -13
88 #define ERR_SW_PARAMS -14
89 #define ERR_READ_OVERRUN -15
91 #define ERR_SHORT_READ -17
92 #define ERR_WRITE_UNDERRUN -18
94 #define ERR_SHORT_WRITE -20
95 #define ERR_TERMINATING -21
98 #define u16 unsigned short
105 const char *id; // in: one of r0, r1, p0, p1
106 char *pcm_name; // in: "default" or "hw:1,0"
107 snd_pcm_stream_t stream; // in: SND_PCM_STREAM_PLAYBACK / SND_PCM_STREAM_CAPTURE
108 snd_pcm_uframes_t start_threshold; // in: start treshold or 0 to keep default
109 snd_pcm_uframes_t stop_threshold; // in: stop treshold or 0 to keep default
110 snd_pcm_uframes_t buffer_size; // in/out: hw buffer size, e.g. 1024 frames
111 snd_pcm_uframes_t period_size; // in/out: period size, e.g. 256 frames
113 snd_pcm_t *handle; // out: pcm handle
114 snd_pcm_hw_params_t *hwparams; // out:
115 snd_pcm_sw_params_t *swparams; // out:
116 int period_buffer_size; // out: size 2000 (256 frames=256 samples, one sample=2bytes)
117 char *period_buffer; // out: allocated buffer for playing/recording
120 /* Dump error on stderr with stream and error description, and return given
122 static int err(const char *msg, int snd_err, struct route_stream *s,
126 return ERR_TERMINATING;
129 fprintf(logfile, "%s (%s): %s", s->id, s->pcm_name, msg);
131 fprintf(logfile, ": %s", snd_strerror(snd_err));
133 fprintf(logfile, "\n");
137 static int open_route_stream(struct route_stream *s)
141 /* Open PCM device for playback. */
142 rc = snd_pcm_open(&(s->handle), s->pcm_name, s->stream, 0);
144 return err("unable to open pcm device", rc, s, ERR_PCM_OPEN_FAILED);
147 /* Allocate a hardware parameters object. */
148 snd_pcm_hw_params_alloca(&(s->hwparams));
150 /* Fill it in with default values. */
151 rc = snd_pcm_hw_params_any(s->handle, s->hwparams);
153 return err("snd_pcm_hw_params_any failed", rc, s, ERR_HW_PARAMS_ANY);
156 /* Interleaved mode */
157 rc = snd_pcm_hw_params_set_access(s->handle, s->hwparams,
158 SND_PCM_ACCESS_RW_INTERLEAVED);
160 return err("snd_pcm_hw_params_set_access failed", rc, s,
161 ERR_HW_PARAMS_SET_ACCESS);
164 /* Signed 16-bit little-endian format */
165 rc = snd_pcm_hw_params_set_format(s->handle, s->hwparams,
166 SND_PCM_FORMAT_S16_LE);
168 return err("snd_pcm_hw_params_set_format failed", rc, s,
169 ERR_HW_PARAMS_SET_FORMAT);
172 /* One channel (mono) */
173 rc = snd_pcm_hw_params_set_channels(s->handle, s->hwparams, 1);
175 return err("snd_pcm_hw_params_set_channels failed", rc, s,
176 ERR_HW_PARAMS_SET_CHANNELS);
179 /* 8000 bits/second sampling rate (umts modem quality) */
180 rc = snd_pcm_hw_params_set_rate(s->handle, s->hwparams, 8000, 0);
182 return err("snd_pcm_hw_params_set_rate_near failed", rc, s,
183 ERR_HW_PARAMS_SET_RATE);
186 /* Period size in frames (e.g. 256) */
187 rc = snd_pcm_hw_params_set_period_size(s->handle, s->hwparams,
190 return err("snd_pcm_hw_params_set_period_size failed", rc, s,
191 ERR_HW_PARAMS_SET_PERIOD_SIZE);
194 /* Buffer size in frames (e.g. 1024) */
195 rc = snd_pcm_hw_params_set_buffer_size(s->handle, s->hwparams,
198 return err("snd_pcm_hw_params_set_buffer_size failed", rc, s,
199 ERR_HW_PARAMS_SET_BUFFER_SIZE);
202 /* Write the parameters to the driver */
203 rc = snd_pcm_hw_params(s->handle, s->hwparams);
205 return err("snd_pcm_hw_params failed", rc, s, ERR_HW_PARAMS);
208 /* Allocate buffer for one period twice as big as period_size because:
209 1 frame = 1 sample = 2 bytes because of S16_LE and 1 channel */
210 s->period_buffer_size = 2 * s->period_size;
211 s->period_buffer = (char *)malloc(s->period_buffer_size);
212 if (s->period_buffer == 0) {
213 return err("period_buffer alloc failed", 0, s, ERR_BUFFER_ALLOC_FAILED);
216 /* Setup software params */
217 if (s->start_threshold > 0 || s->stop_threshold > 0) {
218 snd_pcm_sw_params_alloca(&(s->swparams));
220 rc = snd_pcm_sw_params_current(s->handle, s->swparams);
222 return err("snd_pcm_sw_params_current failed", rc, s,
223 ERR_SW_PARAMS_CURRENT);
226 /* start_threshold */
227 if (s->start_threshold > 0) {
228 rc = snd_pcm_sw_params_set_start_threshold(s->handle,
232 return err("snd_pcm_sw_params_set_start_threshold failed", rc,
233 s, ERR_SW_PARAMS_SET_START_THRESHOLD);
238 if (s->stop_threshold > 0) {
239 rc = snd_pcm_sw_params_set_stop_threshold(s->handle,
243 return err("snd_pcm_sw_params_set_start_threshold failed", rc,
244 s, ERR_SW_PARAMS_SET_STOP_THRESHOLD);
248 rc = snd_pcm_sw_params(s->handle, s->swparams);
250 return err("snd_pcm_sw_params failed", rc, s, ERR_SW_PARAMS);
254 /* Uncomment to dump hw setup */
255 /* static snd_output_t *log;
256 snd_output_stdio_attach(&log, logfile, 0);
257 snd_pcm_dump(s->handle, log);
258 snd_output_close(log); */
263 static int close_route_stream(struct route_stream *s)
265 if (s->handle == 0) {
268 snd_pcm_close(s->handle);
270 if (s->period_buffer == 0) {
273 free(s->period_buffer);
274 s->period_buffer = 0;
278 static void open_route_stream_repeated(struct route_stream *s)
282 rc = open_route_stream(s);
286 close_route_stream(s);
287 fprintf(logfile, "retrying in 100 ms\n");
292 static int route_stream_read(struct route_stream *s)
297 return ERR_TERMINATING;
300 rc = snd_pcm_readi(s->handle, s->period_buffer, s->period_size);
301 if (rc == s->period_size) {
305 /* EPIPE means overrun */
307 err("overrun occured", rc, s, ERR_READ_OVERRUN);
308 snd_pcm_prepare(s->handle);
309 return ERR_READ_OVERRUN;
313 return err("snd_pcm_readi failed", rc, s, ERR_READ);
316 return err("short read", rc, s, ERR_SHORT_READ);
319 static int route_stream_write(struct route_stream *s)
324 return ERR_TERMINATING;
327 rc = snd_pcm_writei(s->handle, s->period_buffer, s->period_size);
328 if (rc == s->period_size) {
332 /* EPIPE means underrun */
334 err("underrun occured", rc, s, ERR_WRITE_UNDERRUN);
335 snd_pcm_prepare(s->handle);
336 return ERR_WRITE_UNDERRUN;
340 return err("snd_pcm_writei failed", rc, s, ERR_WRITE);
343 return err("short write", rc, s, ERR_SHORT_WRITE);
346 /*static void log_with_timestamp(const char *msg)
349 clock_gettime(CLOCK_MONOTONIC, &tp);
350 fprintf(logfile, "%ld %ld: %s\n", tp.tv_sec, tp.tv_nsec, msg);
353 // Write string count bytes long to file
354 static void write_file(const char *path, const char *value, size_t count)
356 int fd = open(path, O_WRONLY);
360 write(fd, value, count);
364 int aux_red_state = 0;
365 int aux_green_state = 0;
367 static void set_aux_leds(int red, int green)
369 if (aux_red_state == red && aux_green_state == green) {
373 aux_green_state = green;
376 write_file("/sys/class/leds/gta04:red:aux/brightness", "255", 3);
378 write_file("/sys/class/leds/gta04:red:aux/brightness", "0", 1);
381 write_file("/sys/class/leds/gta04:green:aux/brightness", "255", 3);
383 write_file("/sys/class/leds/gta04:green:aux/brightness", "0", 1);
387 static void blink_aux()
389 static int last_blink_secs;
392 clock_gettime(CLOCK_MONOTONIC, &tp);
394 if (tp.tv_sec == last_blink_secs) {
397 last_blink_secs = tp.tv_sec;
398 set_aux_leds(!aux_red_state, aux_red_state);
401 static void show_progress()
403 /* static int counter = 0;
404 char ch = "|\\-/"[(counter++) % 4];
406 fputc('\b', logfile);
410 #ifdef USE_WALKIE_TALKIE_AEC
412 static void vol_up(char *buf, snd_pcm_uframes_t period_size)
415 s16 *val = (s16 *) (buf);
416 for (i = 0; i < period_size; i++) {
422 static void vol_down(char *buf, snd_pcm_uframes_t period_size)
425 s16 *val = (s16 *) (buf);
426 for (i = 0; i < period_size; i++) {
427 *val = 0; // *val / 2;
432 /* Reduce echo by adjusting volumes in record and playback buffer with simple
433 walkie-talkie like algorithm.
435 First we decide which buffer has higher volume (i.e. if you are speaking or
436 listening). This buffer is kept as is and we made the other buffer silent or
439 We use integer for computing volumes, this should be fine if the buffer size
440 is not too big (65535 is max).
442 static void reduce_echo(char *p0, char *p1, snd_pcm_uframes_t period_size)
452 for (i = 0; i < period_size; i++) {
459 int diff = sum_p0 - sum_p1;
461 /* 10000 seems to be good limit value. Silence is ~2000 and speech is
462 ~80000. It would be nice to somehow compute this value on the fly, but
463 i have no idea how to do it now */
465 /* fprintf(stderr, "listening p0=%*d p1=%*d diff=%*d\n", -6, sum_p0,
466 -6, sum_p1, -6, diff); */
467 vol_up(p0, period_size);
468 vol_down(p1, period_size);
472 } else if (diff < -10000) {
473 /* fprintf(stderr, "talking p0=%*d p1=%*d diff=%*d\n", -6, sum_p0,
474 -6, sum_p1, -6, diff); */
475 vol_up(p1, period_size);
476 vol_down(p0, period_size);
481 //printf("%d, %d, %d\n", round++, sum_a, sum_b);
485 struct route_stream p0 = {
487 .pcm_name = "default",
488 .stream = SND_PCM_STREAM_PLAYBACK,
489 .start_threshold = 1024,
490 .stop_threshold = 1024,
497 struct route_stream r0 = {
499 .pcm_name = "default",
500 .stream = SND_PCM_STREAM_CAPTURE,
501 .start_threshold = 0,
509 struct route_stream p1 = {
511 .pcm_name = "hw:1,0",
512 .stream = SND_PCM_STREAM_PLAYBACK,
513 .start_threshold = 1024,
514 .stop_threshold = 1024,
521 struct route_stream r1 = {
523 .pcm_name = "hw:1,0",
524 .stream = SND_PCM_STREAM_CAPTURE,
525 .start_threshold = 0,
533 static void cleanup()
535 close_route_stream(&p0);
536 close_route_stream(&p1);
537 close_route_stream(&r0);
538 close_route_stream(&r1);
544 static void sighandler(int signum)
550 fprintf(logfile, "gsm-voice-routing ending - signal %d\n", signum);
561 SpeexEchoState *echo_state;
564 // Register for TERM and interrupt signals
565 signal(SIGINT, sighandler);
566 signal(SIGTERM, sighandler);
568 blink_aux(); // turn red led on so that we know we started
571 logfilename = getenv("GSM_VOICE_ROUTING_LOGFILE");
573 FILE *f = fopen(logfilename, "w");
577 fprintf(stderr, "failed to open logfile %s\n", logfilename);
580 fprintf(logfile, "gsm-voice-routing started\n");
582 /* We want realtime process priority */
585 fprintf(logfile, "nice() failed\n");
588 /* 256=frame (period size), 4096 is filter length (recommended is 1/3 of
589 reverbation time - for 1s it's 8000 / 3 */
590 echo_state = speex_echo_state_init(256, 8192);
593 /* Open streams - umts first */
594 open_route_stream_repeated(&p1);
595 open_route_stream_repeated(&r1);
596 open_route_stream_repeated(&p0);
597 open_route_stream_repeated(&r0);
599 while (route_stream_read(&r1))
601 snd_pcm_start(r0.handle);
602 snd_pcm_start(r1.handle);
605 while (!terminating) {
607 /* Recording - first from internal card (so that we always clean the
608 recording buffer), then UMTS, which can fail */
609 if (route_stream_read(&r0)) {
614 rc = route_stream_read(&r1);
615 if (rc == ERR_READ && started) {
617 "read error after some succesful routing (hangup)\n");
627 fprintf(logfile, "voice routing started\n");
632 speex_echo_cancellation(echo_state, (spx_int16_t *) r0.period_buffer,
633 (spx_int16_t *) p0.period_buffer,
634 (spx_int16_t *) p1.period_buffer);
636 memmove(p0.period_buffer, r1.period_buffer, r1.period_buffer_size);
639 #ifdef USE_WALKIE_TALKIE_AEC
640 memmove(p0.period_buffer, r1.period_buffer, r1.period_buffer_size);
641 memmove(p1.period_buffer, r0.period_buffer, r0.period_buffer_size);
642 reduce_echo(p0.period_buffer, p1.period_buffer, p0.period_size);
645 route_stream_write(&p0);
646 route_stream_write(&p1);
650 speex_echo_state_destroy(echo_state);
653 fprintf(logfile, "gsm-voice-routing ending\n");