From f90f421b165c7880cd88db8795f00073dd768f60 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Sat, 13 Mar 2021 17:10:08 +0100 Subject: Add libs --- src/libsound/sound_alsa.c | 535 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 535 insertions(+) create mode 100644 src/libsound/sound_alsa.c (limited to 'src/libsound/sound_alsa.c') 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 + * 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 "../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::' 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; +} + -- cgit v1.2.3