summaryrefslogtreecommitdiffstats
path: root/src/libsound
diff options
context:
space:
mode:
authorAndreas Eversberg <jolly@eversberg.eu>2021-03-13 17:10:08 +0100
committerAndreas Eversberg <jolly@eversberg.eu>2021-03-21 09:03:06 +0100
commitf90f421b165c7880cd88db8795f00073dd768f60 (patch)
tree1b2c56ae0d2b8b463bb7999229c63b303c64926b /src/libsound
parent72b0d35e6c52a13f133e26b74b2414cf94fbc8e8 (diff)
Add libs
Diffstat (limited to 'src/libsound')
-rw-r--r--src/libsound/Makefile.am8
-rw-r--r--src/libsound/sound.h10
-rw-r--r--src/libsound/sound_alsa.c535
3 files changed, 553 insertions, 0 deletions
diff --git a/src/libsound/Makefile.am b/src/libsound/Makefile.am
new file mode 100644
index 0000000..afbbb0a
--- /dev/null
+++ b/src/libsound/Makefile.am
@@ -0,0 +1,8 @@
+AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
+
+noinst_LIBRARIES = libsound.a
+
+libsound_a_SOURCES = \
+ sound_alsa.c
+
+AM_CPPFLAGS += -DHAVE_ALSA
diff --git a/src/libsound/sound.h b/src/libsound/sound.h
new file mode 100644
index 0000000..6440a3c
--- /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 *am, int channels, double paging_frequency, int samplerate, int latspl, double max_deviation, double max_modulation, double modulation_index);
+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..6216b73
--- /dev/null
+++ b/src/libsound/sound_alsa.c
@@ -0,0 +1,535 @@
+/* Sound device access
+ *
+ * (C) 2016 by Andreas Eversberg <jolly@eversberg.eu>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <math.h>
+#include <alsa/asoundlib.h>
+#include "../libsample/sample.h"
+#include "../libdebug/debug.h"
+#ifdef HAVE_MOBILE
+#include "../libmobile/sender.h"
+#else
+#include "sound.h"
+#endif
+
+typedef struct sound {
+ snd_pcm_t *phandle, *chandle;
+ int pchannels, cchannels;
+ int channels; /* required number of channels */
+ int samplerate; /* required sample rate */
+ char *audiodev; /* required device */
+ double spl_deviation; /* how much deviation is one sample step */
+#ifdef HAVE_MOBILE
+ 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];
+#endif
+} 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 dev_open(sound_t *sound)
+{
+ int rc, rc_rec, rc_play;
+
+ rc_play = snd_pcm_open(&sound->phandle, sound->audiodev, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+ rc_rec = snd_pcm_open(&sound->chandle, sound->audiodev, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
+ if (rc_play < 0 && rc_rec < 0) {
+ PDEBUG(DSOUND, DEBUG_ERROR, "Failed to open '%s'! (%s)\n", sound->audiodev, snd_strerror(rc_play));
+ PDEBUG(DSOUND, DEBUG_ERROR, "Run 'aplay -l' to get a list of available cards and devices.\n");
+ PDEBUG(DSOUND, DEBUG_ERROR, "Then use 'hw:<card>:<device>' for audio device.\n");
+ return rc_play;
+ }
+ if (rc_play < 0) {
+ PDEBUG(DSOUND, DEBUG_ERROR, "Failed to open '%s' for playback! (%s) Please select a device that supports both direction audio.\n", sound->audiodev, snd_strerror(rc_play));
+ return rc_play;
+ }
+ if (rc_rec < 0) {
+ PDEBUG(DSOUND, DEBUG_ERROR, "Failed to open '%s' for capture! (%s) Please select a device that supports both direction audio.\n", sound->audiodev, snd_strerror(rc_rec));
+ return rc_rec;
+ }
+
+ rc = set_hw_params(sound->phandle, sound->samplerate, &sound->pchannels);
+ if (rc < 0) {
+ PDEBUG(DSOUND, DEBUG_ERROR, "Failed to set playback hw params\n");
+ return rc;
+ }
+ if (sound->pchannels < sound->channels) {
+ PDEBUG(DSOUND, DEBUG_ERROR, "Sound card only supports %d channel for playback.\n", sound->pchannels);
+ return rc;
+ }
+ PDEBUG(DSOUND, DEBUG_DEBUG, "Playback with %d channels.\n", sound->pchannels);
+
+ rc = set_hw_params(sound->chandle, sound->samplerate, &sound->cchannels);
+ if (rc < 0) {
+ PDEBUG(DSOUND, DEBUG_ERROR, "Failed to set capture hw params\n");
+ return rc;
+ }
+ if (sound->cchannels < sound->channels) {
+ PDEBUG(DSOUND, DEBUG_ERROR, "Sound card only supports %d channel for capture.\n", sound->cchannels);
+ return -EIO;
+ }
+ PDEBUG(DSOUND, DEBUG_DEBUG, "Capture with %d channels.\n", sound->cchannels);
+
+ 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;
+}
+
+static void dev_close(sound_t *sound)
+{
+ if (sound->phandle != NULL)
+ snd_pcm_close(sound->phandle);
+ if (sound->chandle != NULL)
+ snd_pcm_close(sound->chandle);
+}
+
+void *sound_open(const char *audiodev, double __attribute__((unused)) *tx_frequency, double __attribute__((unused)) *rx_frequency, int __attribute__((unused)) *am, int channels, double __attribute__((unused)) paging_frequency, int samplerate, int __attribute((unused)) latspl, double max_deviation, double __attribute__((unused)) max_modulation, double __attribute__((unused)) modulation_index)
+{
+ 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->audiodev = strdup(audiodev); // is feed when closed
+ sound->channels = channels;
+ sound->samplerate = samplerate;
+ sound->spl_deviation = max_deviation / 32767.0;
+#ifdef HAVE_MOBILE
+ sound->paging_phaseshift = 1.0 / ((double)samplerate / 1000.0);
+#endif
+
+ rc = dev_open(sound);
+ if (rc < 0)
+ goto error;
+
+#ifdef HAVE_MOBILE
+ 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->dispmeas, "RX Level", "%.1f dB", DISPLAY_MEAS_PEAK, DISPLAY_MEAS_LEFT, -96.0, 0.0, -INFINITY);
+ }
+ }
+#endif
+
+ 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;
+
+ dev_close(sound);
+ free(sound->audiodev);
+ free(sound);
+}
+
+#ifdef HAVE_MOBILE
+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;
+ /* FALLTHRU */
+ 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;
+ /* FALLTHRU */
+ 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;
+ }
+}
+#endif
+
+int sound_write(void *inst, sample_t **samples, uint8_t __attribute__((unused)) **power, int num, enum paging_signal __attribute__((unused)) *paging_signal, int __attribute__((unused)) *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 */
+#ifdef HAVE_MOBILE
+ 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
+#endif
+ 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) {
+ dev_close(sound);
+ rc = dev_open(sound);
+ if (rc < 0)
+ return rc;
+ sound_start(sound);
+ return -EPIPE; /* indicate what happened */
+ }
+ 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 __attribute__((unused)) *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;
+
+ /* make valgrind happy, because snd_pcm_readi() does not seem to initially fill buffer with values */
+ memset(buff, 0, sizeof(buff));
+
+ /* 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) {
+ dev_close(sound);
+ rc = dev_open(sound);
+ if (rc < 0)
+ return rc;
+ sound_start(sound);
+ return -EPIPE; /* indicate what happened */
+ }
+ 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;
+ }
+ }
+
+#ifdef HAVE_MOBILE
+ 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);
+ if (rf_level_db)
+ rf_level_db[i] = 0.0;
+ }
+#endif
+
+ 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) {
+ dev_close(sound);
+ rc = dev_open(sound);
+ if (rc < 0)
+ return rc;
+ sound_start(sound);
+ return -EPIPE; /* indicate what happened */
+ }
+ 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;
+}
+