From 45a5568f7032b11953d6ac4321970d5780eaf613 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Fri, 17 Nov 2017 12:16:39 +0100 Subject: Restructure: Move sound from common code to 'libsound' --- .gitignore | 1 + configure.ac | 1 + src/Makefile.am | 2 +- src/amps/Makefile.am | 1 + src/anetz/Makefile.am | 1 + src/bnetz/Makefile.am | 1 + src/cnetz/Makefile.am | 1 + src/common/Makefile.am | 1 - src/common/sender.h | 2 +- src/common/sound.h | 10 - src/common/sound_alsa.c | 485 --------------------------------------------- src/jtacs/Makefile.am | 1 + src/libmncc/mncc_console.c | 2 +- src/libsound/Makefile.am | 6 + src/libsound/sound.h | 10 + src/libsound/sound_alsa.c | 485 +++++++++++++++++++++++++++++++++++++++++++++ src/nmt/Makefile.am | 1 + src/r2000/Makefile.am | 1 + src/tacs/Makefile.am | 1 + src/test/Makefile.am | 4 + 20 files changed, 518 insertions(+), 499 deletions(-) delete mode 100644 src/common/sound.h delete mode 100644 src/common/sound_alsa.c create mode 100644 src/libsound/Makefile.am create mode 100644 src/libsound/sound.h create mode 100644 src/libsound/sound_alsa.c diff --git a/.gitignore b/.gitignore index 855ddbf..5a7ad4e 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ src/libfilter/libfilter.a src/libwave/libwave.a src/libfft/libfft.a src/libmncc/libmncc.a +src/libsound/libsound.a src/common/libcommon.a src/common/libmobile.a src/anetz/libgermanton.a diff --git a/configure.ac b/configure.ac index 7b5779d..8a4e745 100644 --- a/configure.ac +++ b/configure.ac @@ -92,6 +92,7 @@ AC_OUTPUT( src/libwave/Makefile src/libfft/Makefile src/libmncc/Makefile + src/libsound/Makefile src/common/Makefile src/anetz/Makefile src/bnetz/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 708f01e..9b0653e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,3 +1,3 @@ AUTOMAKE_OPTIONS = foreign -SUBDIRS = libimage libcompandor libgoertzel libjitter libsquelch libhagelbarger libdtmf libtimer libsamplerate libscrambler libemphasis libfsk libfm libfilter libwave libfft libmncc common anetz bnetz cnetz nmt amps tacs jtacs r2000 tv test +SUBDIRS = libsound libimage libcompandor libgoertzel libjitter libsquelch libhagelbarger libdtmf libtimer libsamplerate libscrambler libemphasis libfsk libfm libfilter libwave libfft libmncc common anetz bnetz cnetz nmt amps tacs jtacs r2000 tv test diff --git a/src/amps/Makefile.am b/src/amps/Makefile.am index ed9e455..c2fe952 100644 --- a/src/amps/Makefile.am +++ b/src/amps/Makefile.am @@ -39,6 +39,7 @@ amps_LDADD = \ $(top_builddir)/src/libwave/libwave.a \ $(top_builddir)/src/libfft/libfft.a \ $(top_builddir)/src/libmncc/libmncc.a \ + $(top_builddir)/src/libsound/libsound.a \ $(ALSA_LIBS) \ $(UHD_LIBS) \ $(SOAPY_LIBS) \ diff --git a/src/anetz/Makefile.am b/src/anetz/Makefile.am index e4f1dcd..5c9c8d6 100644 --- a/src/anetz/Makefile.am +++ b/src/anetz/Makefile.am @@ -31,6 +31,7 @@ anetz_LDADD = \ $(top_builddir)/src/libwave/libwave.a \ $(top_builddir)/src/libfft/libfft.a \ $(top_builddir)/src/libmncc/libmncc.a \ + $(top_builddir)/src/libsound/libsound.a \ $(ALSA_LIBS) \ $(UHD_LIBS) \ $(SOAPY_LIBS) \ diff --git a/src/bnetz/Makefile.am b/src/bnetz/Makefile.am index 5c89f08..88f7a44 100644 --- a/src/bnetz/Makefile.am +++ b/src/bnetz/Makefile.am @@ -27,6 +27,7 @@ bnetz_LDADD = \ $(top_builddir)/src/libwave/libwave.a \ $(top_builddir)/src/libfft/libfft.a \ $(top_builddir)/src/libmncc/libmncc.a \ + $(top_builddir)/src/libsound/libsound.a \ $(ALSA_LIBS) \ $(UHD_LIBS) \ $(SOAPY_LIBS) \ diff --git a/src/cnetz/Makefile.am b/src/cnetz/Makefile.am index 53ef378..e01c685 100644 --- a/src/cnetz/Makefile.am +++ b/src/cnetz/Makefile.am @@ -30,6 +30,7 @@ cnetz_LDADD = \ $(top_builddir)/src/libwave/libwave.a \ $(top_builddir)/src/libfft/libfft.a \ $(top_builddir)/src/libmncc/libmncc.a \ + $(top_builddir)/src/libsound/libsound.a \ $(ALSA_LIBS) \ $(UHD_LIBS) \ $(SOAPY_LIBS) \ diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 83bc9e4..6d1e07b 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -5,7 +5,6 @@ noinst_LIBRARIES = libcommon.a libmobile.a libcommon_a_SOURCES = \ sample.c \ debug.c \ - sound_alsa.c \ display_wave.c \ display_measurements.c diff --git a/src/common/sender.h b/src/common/sender.h index 09fab06..22a23a1 100644 --- a/src/common/sender.h +++ b/src/common/sender.h @@ -1,4 +1,4 @@ -#include "sound.h" +#include "../libsound/sound.h" #ifdef HAVE_SDR #include "sdr.h" #endif diff --git a/src/common/sound.h b/src/common/sound.h deleted file mode 100644 index 71293c0..0000000 --- a/src/common/sound.h +++ /dev/null @@ -1,10 +0,0 @@ - -enum paging_signal; - -void *sound_open(const char *audiodev, double *tx_frequency, double *rx_frequency, int channels, double paging_frequency, int samplerate, int latspl, double bandwidth, double sample_deviation); -int sound_start(void *inst); -void sound_close(void *inst); -int sound_write(void *inst, sample_t **samples, uint8_t **power, int num, enum paging_signal *paging_signal, int *on, int channels); -int sound_read(void *inst, sample_t **samples, int num, int channels, double *rf_level_db); -int sound_get_tosend(void *inst, int latspl); - diff --git a/src/common/sound_alsa.c b/src/common/sound_alsa.c deleted file mode 100644 index c18f123..0000000 --- a/src/common/sound_alsa.c +++ /dev/null @@ -1,485 +0,0 @@ -/* Sound device access - * - * (C) 2016 by Andreas Eversberg - * All Rights Reserved - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include -#include "sample.h" -#include "debug.h" -#include "sender.h" - -typedef struct sound { - snd_pcm_t *phandle, *chandle; - int pchannels, cchannels; - double spl_deviation; /* how much deviation is one sample step */ - double paging_phaseshift; /* phase to shift every sample */ - double paging_phase; /* current phase */ - double rx_frequency[2]; /* rx frequency of radio connected to channel */ - dispmeasparam_t *dmp[2]; -} sound_t; - -static int set_hw_params(snd_pcm_t *handle, int samplerate, int *channels) -{ - snd_pcm_hw_params_t *hw_params = NULL; - int rc; - unsigned int rrate; - - rc = snd_pcm_hw_params_malloc(&hw_params); - if (rc < 0) { - PDEBUG(DSOUND, DEBUG_ERROR, "Failed to allocate hw_params! (%s)\n", snd_strerror(rc)); - goto error; - } - - rc = snd_pcm_hw_params_any(handle, hw_params); - if (rc < 0) { - PDEBUG(DSOUND, DEBUG_ERROR, "cannot initialize hardware parameter structure (%s)\n", snd_strerror(rc)); - goto error; - } - - rc = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0); - if (rc < 0) { - PDEBUG(DSOUND, DEBUG_ERROR, "cannot set real hardware rate (%s)\n", snd_strerror(rc)); - goto error; - } - - rc = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); - if (rc < 0) { - PDEBUG(DSOUND, DEBUG_ERROR, "cannot set access to interleaved (%s)\n", snd_strerror(rc)); - goto error; - } - - rc = snd_pcm_hw_params_set_format(handle, hw_params, SND_PCM_FORMAT_S16); - if (rc < 0) { - PDEBUG(DSOUND, DEBUG_ERROR, "cannot set sample format (%s)\n", snd_strerror(rc)); - goto error; - } - - rrate = samplerate; - rc = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0); - if (rc < 0) { - PDEBUG(DSOUND, DEBUG_ERROR, "cannot set sample rate (%s)\n", snd_strerror(rc)); - goto error; - } - if ((int)rrate != samplerate) { - PDEBUG(DSOUND, DEBUG_ERROR, "Rate doesn't match (requested %dHz, get %dHz)\n", samplerate, rrate); - rc = -EIO; - goto error; - } - - *channels = 1; - rc = snd_pcm_hw_params_set_channels(handle, hw_params, *channels); - if (rc < 0) { - *channels = 2; - rc = snd_pcm_hw_params_set_channels(handle, hw_params, *channels); - if (rc < 0) { - PDEBUG(DSOUND, DEBUG_ERROR, "cannot set channel count to 1 nor 2 (%s)\n", snd_strerror(rc)); - goto error; - } - } - - rc = snd_pcm_hw_params(handle, hw_params); - if (rc < 0) { - PDEBUG(DSOUND, DEBUG_ERROR, "cannot set parameters (%s)\n", snd_strerror(rc)); - goto error; - } - - snd_pcm_hw_params_free(hw_params); - - return 0; - -error: - if (hw_params) { - snd_pcm_hw_params_free(hw_params); - } - - return rc; -} - -static int sound_prepare(sound_t *sound) -{ - int rc; - - rc = snd_pcm_prepare(sound->phandle); - if (rc < 0) { - PDEBUG(DSOUND, DEBUG_ERROR, "cannot prepare audio interface for use (%s)\n", snd_strerror(rc)); - return rc; - } - - rc = snd_pcm_prepare(sound->chandle); - if (rc < 0) { - PDEBUG(DSOUND, DEBUG_ERROR, "cannot prepare audio interface for use (%s)\n", snd_strerror(rc)); - return rc; - } - - return 0; -} - -void *sound_open(const char *audiodev, double __attribute__((unused)) *tx_frequency, double __attribute__((unused)) *rx_frequency, int channels, double __attribute__((unused)) paging_frequency, int samplerate, int __attribute((unused)) latspl, double max_deviation, double __attribute__((unused)) max_modulation) -{ - sound_t *sound; - int rc; - - if (channels < 1 || channels > 2) { - PDEBUG(DSOUND, DEBUG_ERROR, "Cannot use more than two channels with the same sound card!\n"); - return NULL; - } - - sound = calloc(1, sizeof(sound_t)); - if (!sound) { - PDEBUG(DSOUND, DEBUG_ERROR, "Failed to alloc memory!\n"); - return NULL; - } - - sound->spl_deviation = max_deviation / 32767.0; - sound->paging_phaseshift = 1.0 / ((double)samplerate / 1000.0); - - rc = snd_pcm_open(&sound->phandle, audiodev, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); - if (rc < 0) { - PDEBUG(DSOUND, DEBUG_ERROR, "Failed to open '%s' for playback! (%s)\n", audiodev, snd_strerror(rc)); - goto error; - } - - rc = snd_pcm_open(&sound->chandle, audiodev, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); - if (rc < 0) { - PDEBUG(DSOUND, DEBUG_ERROR, "Failed to open '%s' for capture! (%s)\n", audiodev, snd_strerror(rc)); - goto error; - } - - rc = set_hw_params(sound->phandle, samplerate, &sound->pchannels); - if (rc < 0) { - PDEBUG(DSOUND, DEBUG_ERROR, "Failed to set playback hw params\n"); - goto error; - } - if (sound->pchannels < channels) { - PDEBUG(DSOUND, DEBUG_ERROR, "Sound card only supports %d channel for playback.\n", sound->pchannels); - goto error; - } - PDEBUG(DSOUND, DEBUG_DEBUG, "Playback with %d channels.\n", sound->pchannels); - - rc = set_hw_params(sound->chandle, samplerate, &sound->cchannels); - if (rc < 0) { - PDEBUG(DSOUND, DEBUG_ERROR, "Failed to set capture hw params\n"); - goto error; - } - if (sound->cchannels < channels) { - PDEBUG(DSOUND, DEBUG_ERROR, "Sound card only supports %d channel for capture.\n", sound->cchannels); - goto error; - } - PDEBUG(DSOUND, DEBUG_DEBUG, "Capture with %d channels.\n", sound->cchannels); - - rc = sound_prepare(sound); - if (rc < 0) - goto error; - - if (rx_frequency) { - sender_t *sender; - int i; - for (i = 0; i < channels; i++) { - sound->rx_frequency[i] = rx_frequency[i]; - sender = get_sender_by_empfangsfrequenz(sound->rx_frequency[i]); - if (!sender) - continue; - sound->dmp[i] = display_measurements_add(sender, "RX Level", "%.1f dB", DISPLAY_MEAS_PEAK, DISPLAY_MEAS_LEFT, -96.0, 0.0, -INFINITY); - } - } - - return sound; - -error: - sound_close(sound); - return NULL; -} - -/* start streaming */ -int sound_start(void *inst) -{ - sound_t *sound = (sound_t *)inst; - int16_t buff[2]; - - /* trigger capturing */ - snd_pcm_readi(sound->chandle, buff, 1); - - return 0; -} - -void sound_close(void *inst) -{ - sound_t *sound = (sound_t *)inst; - - if (sound->phandle != NULL) - snd_pcm_close(sound->phandle); - if (sound->chandle != NULL) - snd_pcm_close(sound->chandle); - free(sound); -} - -static void gen_paging_tone(sound_t *sound, int16_t *samples, int length, enum paging_signal paging_signal, int on) -{ - double phaseshift, phase; - int i; - - switch (paging_signal) { - case PAGING_SIGNAL_NOTONE: - /* no tone if paging signal is on */ - on = !on; - // fall through - case PAGING_SIGNAL_TONE: - /* tone if paging signal is on */ - if (on) { - phaseshift = sound->paging_phaseshift; - phase = sound->paging_phase; - for (i = 0; i < length; i++) { - if (phase < 0.5) - *samples++ = 30000; - else - *samples++ = -30000; - phase += phaseshift; - if (phase >= 1.0) - phase -= 1.0; - } - sound->paging_phase = phase; - } else - memset(samples, 0, length << 1); - break; - case PAGING_SIGNAL_NEGATIVE: - /* negative signal if paging signal is on */ - on = !on; - // fall through - case PAGING_SIGNAL_POSITIVE: - /* positive signal if paging signal is on */ - if (on) - memset(samples, 127, length << 1); - else - memset(samples, 128, length << 1); - break; - case PAGING_SIGNAL_NONE: - break; - } -} - -int sound_write(void *inst, sample_t **samples, uint8_t __attribute__((unused)) **power, int num, enum paging_signal *paging_signal, int *on, int channels) -{ - sound_t *sound = (sound_t *)inst; - double spl_deviation = sound->spl_deviation; - int32_t value; - int16_t buff[num << 1]; - int rc; - int i, ii; - - if (sound->pchannels == 2) { - /* two channels */ - if (paging_signal && on && paging_signal[0] != PAGING_SIGNAL_NONE) { - int16_t paging[num << 1]; - gen_paging_tone(sound, paging, num, paging_signal[0], on[0]); - for (i = 0, ii = 0; i < num; i++) { - value = samples[0][i] / spl_deviation; - if (value > 32767) - value = 32767; - else if (value < -32767) - value = -32767; - buff[ii++] = value; - buff[ii++] = paging[i]; - } - } else if (channels == 2) { - for (i = 0, ii = 0; i < num; i++) { - value = samples[0][i] / spl_deviation; - if (value > 32767) - value = 32767; - else if (value < -32767) - value = -32767; - buff[ii++] = value; - value = samples[1][i] / spl_deviation; - if (value > 32767) - value = 32767; - else if (value < -32767) - value = -32767; - buff[ii++] = value; - } - } else { - for (i = 0, ii = 0; i < num; i++) { - value = samples[0][i] / spl_deviation; - if (value > 32767) - value = 32767; - else if (value < -32767) - value = -32767; - buff[ii++] = value; - buff[ii++] = value; - } - } - } else { - /* one channel */ - for (i = 0, ii = 0; i < num; i++) { - value = samples[0][i] / spl_deviation; - if (value > 32767) - value = 32767; - else if (value < -32767) - value = -32767; - buff[ii++] = value; - } - } - rc = snd_pcm_writei(sound->phandle, buff, num); - - if (rc < 0) { - PDEBUG(DSOUND, DEBUG_ERROR, "failed to write audio to interface (%s)\n", snd_strerror(rc)); - if (rc == -EPIPE) { - sound_prepare(sound); - sound_start(sound); - } - return rc; - } - - if (rc != num) - PDEBUG(DSOUND, DEBUG_ERROR, "short write to audio interface, written %d bytes, got %d bytes\n", num, rc); - - return rc; -} - -#define KEEP_FRAMES 8 /* minimum frames not to read, due to bug in ALSA */ - -int sound_read(void *inst, sample_t **samples, int num, int channels, double *rf_level_db) -{ - sound_t *sound = (sound_t *)inst; - double spl_deviation = sound->spl_deviation; - int16_t buff[num << 1]; - int32_t spl; - int32_t max[2], a; - int in, rc; - int i, ii; - - /* get samples in rx buffer */ - in = snd_pcm_avail(sound->chandle); - /* if not more than KEEP_FRAMES frames available, try next time */ - if (in <= KEEP_FRAMES) - return 0; - /* read some frames less than in buffer, because snd_pcm_readi() seems - * to corrupt last frames */ - in -= KEEP_FRAMES; - if (in > num) - in = num; - - rc = snd_pcm_readi(sound->chandle, buff, in); - - if (rc < 0) { - if (errno == EAGAIN) - return 0; - PDEBUG(DSOUND, DEBUG_ERROR, "failed to read audio from interface (%s)\n", snd_strerror(rc)); - /* recover read */ - if (rc == -EPIPE) { - sound_prepare(sound); - sound_start(sound); - } - return rc; - } - - if (rc == 0) - return rc; - - if (sound->cchannels == 2) { - if (channels < 2) { - for (i = 0, ii = 0; i < rc; i++) { - spl = buff[ii++]; - spl += buff[ii++]; - a = (spl >= 0) ? spl : -spl; - if (i == 0 || a > max[0]) - max[0] = a; - samples[0][i] = (double)spl * spl_deviation; - } - } else { - for (i = 0, ii = 0; i < rc; i++) { - spl = buff[ii++]; - a = (spl >= 0) ? spl : -spl; - if (i == 0 || a > max[0]) - max[0] = a; - samples[0][i] = (double)spl * spl_deviation; - spl = buff[ii++]; - a = (spl >= 0) ? spl : -spl; - if (i == 0 || a > max[1]) - max[1] = a; - samples[1][i] = (double)spl * spl_deviation; - } - } - } else { - for (i = 0, ii = 0; i < rc; i++) { - spl = buff[ii++]; - a = (spl >= 0) ? spl : -spl; - if (i == 0 || a > max[0]) - max[0] = a; - samples[0][i] = (double)spl * spl_deviation; - } - } - - sender_t *sender; - for (i = 0; i < channels; i++) { - sender = get_sender_by_empfangsfrequenz(sound->rx_frequency[i]); - if (!sender) - continue; - display_measurements_update(sound->dmp[i], log10((double)max[i] / 32768.0) * 20, 0.0); - rf_level_db[i] = 0.0; - } - - return rc; -} - -/* - * get playback buffer space - * - * return number of samples to be sent */ -int sound_get_tosend(void *inst, int latspl) -{ - sound_t *sound = (sound_t *)inst; - int rc; - snd_pcm_sframes_t delay; - int tosend; - - rc = snd_pcm_delay(sound->phandle, &delay); - if (rc < 0) { - if (rc == -32) - PDEBUG(DSOUND, DEBUG_ERROR, "Buffer underrun: Please use higher latency and enable real time scheduling\n"); - else - PDEBUG(DSOUND, DEBUG_ERROR, "failed to get delay from interface (%s)\n", snd_strerror(rc)); - if (rc == -EPIPE) { - sound_prepare(sound); - sound_start(sound); - } - return rc; - } - - tosend = latspl - delay; - return tosend; -} - -int sound_is_stereo_capture(void *inst) -{ - sound_t *sound = (sound_t *)inst; - - if (sound->cchannels == 2) - return 1; - return 0; -} - -int sound_is_stereo_playback(void *inst) -{ - sound_t *sound = (sound_t *)inst; - - if (sound->pchannels == 2) - return 1; - return 0; -} - diff --git a/src/jtacs/Makefile.am b/src/jtacs/Makefile.am index d440d8b..61e7a44 100644 --- a/src/jtacs/Makefile.am +++ b/src/jtacs/Makefile.am @@ -25,6 +25,7 @@ jtacs_LDADD = \ $(top_builddir)/src/libwave/libwave.a \ $(top_builddir)/src/libfft/libfft.a \ $(top_builddir)/src/libmncc/libmncc.a \ + $(top_builddir)/src/libsound/libsound.a \ $(ALSA_LIBS) \ $(UHD_LIBS) \ $(SOAPY_LIBS) \ diff --git a/src/libmncc/mncc_console.c b/src/libmncc/mncc_console.c index 41682aa..ac356c3 100644 --- a/src/libmncc/mncc_console.c +++ b/src/libmncc/mncc_console.c @@ -33,7 +33,7 @@ #include "mncc_console.h" #include "cause.h" #include "../common/call.h" -#include "../common/sound.h" +#include "../libsound/sound.h" static int new_callref = 0; /* toward mobile */ diff --git a/src/libsound/Makefile.am b/src/libsound/Makefile.am new file mode 100644 index 0000000..ffa876f --- /dev/null +++ b/src/libsound/Makefile.am @@ -0,0 +1,6 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +noinst_LIBRARIES = libsound.a + +libsound_a_SOURCES = \ + sound_alsa.c diff --git a/src/libsound/sound.h b/src/libsound/sound.h new file mode 100644 index 0000000..71293c0 --- /dev/null +++ b/src/libsound/sound.h @@ -0,0 +1,10 @@ + +enum paging_signal; + +void *sound_open(const char *audiodev, double *tx_frequency, double *rx_frequency, int channels, double paging_frequency, int samplerate, int latspl, double bandwidth, double sample_deviation); +int sound_start(void *inst); +void sound_close(void *inst); +int sound_write(void *inst, sample_t **samples, uint8_t **power, int num, enum paging_signal *paging_signal, int *on, int channels); +int sound_read(void *inst, sample_t **samples, int num, int channels, double *rf_level_db); +int sound_get_tosend(void *inst, int latspl); + diff --git a/src/libsound/sound_alsa.c b/src/libsound/sound_alsa.c new file mode 100644 index 0000000..a0fe878 --- /dev/null +++ b/src/libsound/sound_alsa.c @@ -0,0 +1,485 @@ +/* Sound device access + * + * (C) 2016 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include "../common/sample.h" +#include "../common/debug.h" +#include "../common/sender.h" + +typedef struct sound { + snd_pcm_t *phandle, *chandle; + int pchannels, cchannels; + double spl_deviation; /* how much deviation is one sample step */ + double paging_phaseshift; /* phase to shift every sample */ + double paging_phase; /* current phase */ + double rx_frequency[2]; /* rx frequency of radio connected to channel */ + dispmeasparam_t *dmp[2]; +} sound_t; + +static int set_hw_params(snd_pcm_t *handle, int samplerate, int *channels) +{ + snd_pcm_hw_params_t *hw_params = NULL; + int rc; + unsigned int rrate; + + rc = snd_pcm_hw_params_malloc(&hw_params); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "Failed to allocate hw_params! (%s)\n", snd_strerror(rc)); + goto error; + } + + rc = snd_pcm_hw_params_any(handle, hw_params); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot initialize hardware parameter structure (%s)\n", snd_strerror(rc)); + goto error; + } + + rc = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot set real hardware rate (%s)\n", snd_strerror(rc)); + goto error; + } + + rc = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot set access to interleaved (%s)\n", snd_strerror(rc)); + goto error; + } + + rc = snd_pcm_hw_params_set_format(handle, hw_params, SND_PCM_FORMAT_S16); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot set sample format (%s)\n", snd_strerror(rc)); + goto error; + } + + rrate = samplerate; + rc = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot set sample rate (%s)\n", snd_strerror(rc)); + goto error; + } + if ((int)rrate != samplerate) { + PDEBUG(DSOUND, DEBUG_ERROR, "Rate doesn't match (requested %dHz, get %dHz)\n", samplerate, rrate); + rc = -EIO; + goto error; + } + + *channels = 1; + rc = snd_pcm_hw_params_set_channels(handle, hw_params, *channels); + if (rc < 0) { + *channels = 2; + rc = snd_pcm_hw_params_set_channels(handle, hw_params, *channels); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot set channel count to 1 nor 2 (%s)\n", snd_strerror(rc)); + goto error; + } + } + + rc = snd_pcm_hw_params(handle, hw_params); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot set parameters (%s)\n", snd_strerror(rc)); + goto error; + } + + snd_pcm_hw_params_free(hw_params); + + return 0; + +error: + if (hw_params) { + snd_pcm_hw_params_free(hw_params); + } + + return rc; +} + +static int sound_prepare(sound_t *sound) +{ + int rc; + + rc = snd_pcm_prepare(sound->phandle); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot prepare audio interface for use (%s)\n", snd_strerror(rc)); + return rc; + } + + rc = snd_pcm_prepare(sound->chandle); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot prepare audio interface for use (%s)\n", snd_strerror(rc)); + return rc; + } + + return 0; +} + +void *sound_open(const char *audiodev, double __attribute__((unused)) *tx_frequency, double __attribute__((unused)) *rx_frequency, int channels, double __attribute__((unused)) paging_frequency, int samplerate, int __attribute((unused)) latspl, double max_deviation, double __attribute__((unused)) max_modulation) +{ + sound_t *sound; + int rc; + + if (channels < 1 || channels > 2) { + PDEBUG(DSOUND, DEBUG_ERROR, "Cannot use more than two channels with the same sound card!\n"); + return NULL; + } + + sound = calloc(1, sizeof(sound_t)); + if (!sound) { + PDEBUG(DSOUND, DEBUG_ERROR, "Failed to alloc memory!\n"); + return NULL; + } + + sound->spl_deviation = max_deviation / 32767.0; + sound->paging_phaseshift = 1.0 / ((double)samplerate / 1000.0); + + rc = snd_pcm_open(&sound->phandle, audiodev, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "Failed to open '%s' for playback! (%s)\n", audiodev, snd_strerror(rc)); + goto error; + } + + rc = snd_pcm_open(&sound->chandle, audiodev, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "Failed to open '%s' for capture! (%s)\n", audiodev, snd_strerror(rc)); + goto error; + } + + rc = set_hw_params(sound->phandle, samplerate, &sound->pchannels); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "Failed to set playback hw params\n"); + goto error; + } + if (sound->pchannels < channels) { + PDEBUG(DSOUND, DEBUG_ERROR, "Sound card only supports %d channel for playback.\n", sound->pchannels); + goto error; + } + PDEBUG(DSOUND, DEBUG_DEBUG, "Playback with %d channels.\n", sound->pchannels); + + rc = set_hw_params(sound->chandle, samplerate, &sound->cchannels); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "Failed to set capture hw params\n"); + goto error; + } + if (sound->cchannels < channels) { + PDEBUG(DSOUND, DEBUG_ERROR, "Sound card only supports %d channel for capture.\n", sound->cchannels); + goto error; + } + PDEBUG(DSOUND, DEBUG_DEBUG, "Capture with %d channels.\n", sound->cchannels); + + rc = sound_prepare(sound); + if (rc < 0) + goto error; + + if (rx_frequency) { + sender_t *sender; + int i; + for (i = 0; i < channels; i++) { + sound->rx_frequency[i] = rx_frequency[i]; + sender = get_sender_by_empfangsfrequenz(sound->rx_frequency[i]); + if (!sender) + continue; + sound->dmp[i] = display_measurements_add(sender, "RX Level", "%.1f dB", DISPLAY_MEAS_PEAK, DISPLAY_MEAS_LEFT, -96.0, 0.0, -INFINITY); + } + } + + return sound; + +error: + sound_close(sound); + return NULL; +} + +/* start streaming */ +int sound_start(void *inst) +{ + sound_t *sound = (sound_t *)inst; + int16_t buff[2]; + + /* trigger capturing */ + snd_pcm_readi(sound->chandle, buff, 1); + + return 0; +} + +void sound_close(void *inst) +{ + sound_t *sound = (sound_t *)inst; + + if (sound->phandle != NULL) + snd_pcm_close(sound->phandle); + if (sound->chandle != NULL) + snd_pcm_close(sound->chandle); + free(sound); +} + +static void gen_paging_tone(sound_t *sound, int16_t *samples, int length, enum paging_signal paging_signal, int on) +{ + double phaseshift, phase; + int i; + + switch (paging_signal) { + case PAGING_SIGNAL_NOTONE: + /* no tone if paging signal is on */ + on = !on; + // fall through + case PAGING_SIGNAL_TONE: + /* tone if paging signal is on */ + if (on) { + phaseshift = sound->paging_phaseshift; + phase = sound->paging_phase; + for (i = 0; i < length; i++) { + if (phase < 0.5) + *samples++ = 30000; + else + *samples++ = -30000; + phase += phaseshift; + if (phase >= 1.0) + phase -= 1.0; + } + sound->paging_phase = phase; + } else + memset(samples, 0, length << 1); + break; + case PAGING_SIGNAL_NEGATIVE: + /* negative signal if paging signal is on */ + on = !on; + // fall through + case PAGING_SIGNAL_POSITIVE: + /* positive signal if paging signal is on */ + if (on) + memset(samples, 127, length << 1); + else + memset(samples, 128, length << 1); + break; + case PAGING_SIGNAL_NONE: + break; + } +} + +int sound_write(void *inst, sample_t **samples, uint8_t __attribute__((unused)) **power, int num, enum paging_signal *paging_signal, int *on, int channels) +{ + sound_t *sound = (sound_t *)inst; + double spl_deviation = sound->spl_deviation; + int32_t value; + int16_t buff[num << 1]; + int rc; + int i, ii; + + if (sound->pchannels == 2) { + /* two channels */ + if (paging_signal && on && paging_signal[0] != PAGING_SIGNAL_NONE) { + int16_t paging[num << 1]; + gen_paging_tone(sound, paging, num, paging_signal[0], on[0]); + for (i = 0, ii = 0; i < num; i++) { + value = samples[0][i] / spl_deviation; + if (value > 32767) + value = 32767; + else if (value < -32767) + value = -32767; + buff[ii++] = value; + buff[ii++] = paging[i]; + } + } else if (channels == 2) { + for (i = 0, ii = 0; i < num; i++) { + value = samples[0][i] / spl_deviation; + if (value > 32767) + value = 32767; + else if (value < -32767) + value = -32767; + buff[ii++] = value; + value = samples[1][i] / spl_deviation; + if (value > 32767) + value = 32767; + else if (value < -32767) + value = -32767; + buff[ii++] = value; + } + } else { + for (i = 0, ii = 0; i < num; i++) { + value = samples[0][i] / spl_deviation; + if (value > 32767) + value = 32767; + else if (value < -32767) + value = -32767; + buff[ii++] = value; + buff[ii++] = value; + } + } + } else { + /* one channel */ + for (i = 0, ii = 0; i < num; i++) { + value = samples[0][i] / spl_deviation; + if (value > 32767) + value = 32767; + else if (value < -32767) + value = -32767; + buff[ii++] = value; + } + } + rc = snd_pcm_writei(sound->phandle, buff, num); + + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "failed to write audio to interface (%s)\n", snd_strerror(rc)); + if (rc == -EPIPE) { + sound_prepare(sound); + sound_start(sound); + } + return rc; + } + + if (rc != num) + PDEBUG(DSOUND, DEBUG_ERROR, "short write to audio interface, written %d bytes, got %d bytes\n", num, rc); + + return rc; +} + +#define KEEP_FRAMES 8 /* minimum frames not to read, due to bug in ALSA */ + +int sound_read(void *inst, sample_t **samples, int num, int channels, double *rf_level_db) +{ + sound_t *sound = (sound_t *)inst; + double spl_deviation = sound->spl_deviation; + int16_t buff[num << 1]; + int32_t spl; + int32_t max[2], a; + int in, rc; + int i, ii; + + /* get samples in rx buffer */ + in = snd_pcm_avail(sound->chandle); + /* if not more than KEEP_FRAMES frames available, try next time */ + if (in <= KEEP_FRAMES) + return 0; + /* read some frames less than in buffer, because snd_pcm_readi() seems + * to corrupt last frames */ + in -= KEEP_FRAMES; + if (in > num) + in = num; + + rc = snd_pcm_readi(sound->chandle, buff, in); + + if (rc < 0) { + if (errno == EAGAIN) + return 0; + PDEBUG(DSOUND, DEBUG_ERROR, "failed to read audio from interface (%s)\n", snd_strerror(rc)); + /* recover read */ + if (rc == -EPIPE) { + sound_prepare(sound); + sound_start(sound); + } + return rc; + } + + if (rc == 0) + return rc; + + if (sound->cchannels == 2) { + if (channels < 2) { + for (i = 0, ii = 0; i < rc; i++) { + spl = buff[ii++]; + spl += buff[ii++]; + a = (spl >= 0) ? spl : -spl; + if (i == 0 || a > max[0]) + max[0] = a; + samples[0][i] = (double)spl * spl_deviation; + } + } else { + for (i = 0, ii = 0; i < rc; i++) { + spl = buff[ii++]; + a = (spl >= 0) ? spl : -spl; + if (i == 0 || a > max[0]) + max[0] = a; + samples[0][i] = (double)spl * spl_deviation; + spl = buff[ii++]; + a = (spl >= 0) ? spl : -spl; + if (i == 0 || a > max[1]) + max[1] = a; + samples[1][i] = (double)spl * spl_deviation; + } + } + } else { + for (i = 0, ii = 0; i < rc; i++) { + spl = buff[ii++]; + a = (spl >= 0) ? spl : -spl; + if (i == 0 || a > max[0]) + max[0] = a; + samples[0][i] = (double)spl * spl_deviation; + } + } + + sender_t *sender; + for (i = 0; i < channels; i++) { + sender = get_sender_by_empfangsfrequenz(sound->rx_frequency[i]); + if (!sender) + continue; + display_measurements_update(sound->dmp[i], log10((double)max[i] / 32768.0) * 20, 0.0); + rf_level_db[i] = 0.0; + } + + return rc; +} + +/* + * get playback buffer space + * + * return number of samples to be sent */ +int sound_get_tosend(void *inst, int latspl) +{ + sound_t *sound = (sound_t *)inst; + int rc; + snd_pcm_sframes_t delay; + int tosend; + + rc = snd_pcm_delay(sound->phandle, &delay); + if (rc < 0) { + if (rc == -32) + PDEBUG(DSOUND, DEBUG_ERROR, "Buffer underrun: Please use higher latency and enable real time scheduling\n"); + else + PDEBUG(DSOUND, DEBUG_ERROR, "failed to get delay from interface (%s)\n", snd_strerror(rc)); + if (rc == -EPIPE) { + sound_prepare(sound); + sound_start(sound); + } + return rc; + } + + tosend = latspl - delay; + return tosend; +} + +int sound_is_stereo_capture(void *inst) +{ + sound_t *sound = (sound_t *)inst; + + if (sound->cchannels == 2) + return 1; + return 0; +} + +int sound_is_stereo_playback(void *inst) +{ + sound_t *sound = (sound_t *)inst; + + if (sound->pchannels == 2) + return 1; + return 0; +} + diff --git a/src/nmt/Makefile.am b/src/nmt/Makefile.am index 80d0026..d6ad99c 100644 --- a/src/nmt/Makefile.am +++ b/src/nmt/Makefile.am @@ -38,6 +38,7 @@ nmt_LDADD = \ $(top_builddir)/src/libwave/libwave.a \ $(top_builddir)/src/libfft/libfft.a \ $(top_builddir)/src/libmncc/libmncc.a \ + $(top_builddir)/src/libsound/libsound.a \ $(ALSA_LIBS) \ $(UHD_LIBS) \ $(SOAPY_LIBS) \ diff --git a/src/r2000/Makefile.am b/src/r2000/Makefile.am index 3a3b059..4066ff7 100644 --- a/src/r2000/Makefile.am +++ b/src/r2000/Makefile.am @@ -26,6 +26,7 @@ radiocom2000_LDADD = \ $(top_builddir)/src/libwave/libwave.a \ $(top_builddir)/src/libfft/libfft.a \ $(top_builddir)/src/libmncc/libmncc.a \ + $(top_builddir)/src/libsound/libsound.a \ $(ALSA_LIBS) \ $(UHD_LIBS) \ $(SOAPY_LIBS) \ diff --git a/src/tacs/Makefile.am b/src/tacs/Makefile.am index a79bf8e..62d681f 100644 --- a/src/tacs/Makefile.am +++ b/src/tacs/Makefile.am @@ -26,6 +26,7 @@ tacs_LDADD = \ $(top_builddir)/src/libwave/libwave.a \ $(top_builddir)/src/libfft/libfft.a \ $(top_builddir)/src/libmncc/libmncc.a \ + $(top_builddir)/src/libsound/libsound.a \ $(ALSA_LIBS) \ $(UHD_LIBS) \ $(SOAPY_LIBS) \ diff --git a/src/test/Makefile.am b/src/test/Makefile.am index d0507fa..dfdfd5f 100644 --- a/src/test/Makefile.am +++ b/src/test/Makefile.am @@ -24,6 +24,7 @@ test_filter_LDADD = \ $(top_builddir)/src/libwave/libwave.a \ $(top_builddir)/src/libfft/libfft.a \ $(top_builddir)/src/libmncc/libmncc.a \ + $(top_builddir)/src/libsound/libsound.a \ $(ALSA_LIBS) \ $(UHD_LIBS) \ $(SOAPY_LIBS) \ @@ -56,6 +57,7 @@ test_emphasis_LDADD = \ $(top_builddir)/src/libwave/libwave.a \ $(top_builddir)/src/libfft/libfft.a \ $(top_builddir)/src/libmncc/libmncc.a \ + $(top_builddir)/src/libsound/libsound.a \ $(ALSA_LIBS) \ $(UHD_LIBS) \ $(SOAPY_LIBS) \ @@ -77,6 +79,7 @@ test_dms_LDADD = \ $(top_builddir)/src/libwave/libwave.a \ $(top_builddir)/src/libfft/libfft.a \ $(top_builddir)/src/libmncc/libmncc.a \ + $(top_builddir)/src/libsound/libsound.a \ $(ALSA_LIBS) \ $(UHD_LIBS) \ $(SOAPY_LIBS) \ @@ -98,6 +101,7 @@ test_sms_LDADD = \ $(top_builddir)/src/libwave/libwave.a \ $(top_builddir)/src/libfft/libfft.a \ $(top_builddir)/src/libmncc/libmncc.a \ + $(top_builddir)/src/libsound/libsound.a \ $(ALSA_LIBS) \ $(UHD_LIBS) \ $(SOAPY_LIBS) \ -- cgit v1.2.3