From 28bd1268066b6bb75214c40616d3c3b24c6a2ea8 Mon Sep 17 00:00:00 2001 From: Landry Breuil Date: Sat, 10 Jul 2021 15:19:14 +0200 Subject: [PATCH] add sndio audiofilter --- CMakeLists.txt | 13 +- include/mediastreamer2/allfilters.h | 2 + mediastreamer-config.h.cmake | 1 + src/CMakeLists.txt | 3 + src/audiofilters/sndio.c | 462 ++++++++++++++++++++++++++++ src/voip/msvoip.c | 8 + 6 files changed, 488 insertions(+), 1 deletion(-) create mode 100644 src/audiofilters/sndio.c diff --git a/CMakeLists.txt b/CMakeLists.txt index c2303a53ba..360a9ccd34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,7 @@ cmake_dependent_option(ENABLE_QSA "Enable QSA (QNX Sound Architecture) support." cmake_dependent_option(ENABLE_OSS "Enable OSS support." NO "ENABLE_SOUND;LINUX_OR_BSD" NO) cmake_dependent_option(ENABLE_PORTAUDIO "Enable portaudio native support." NO "ENABLE_SOUND" NO) cmake_dependent_option(ENABLE_PULSEAUDIO "Enable pulseaudio support." YES "ENABLE_SOUND" NO) +cmake_dependent_option(ENABLE_SNDIO "Enable sndio support." YES "ENABLE_SOUND" NO) option(ENABLE_G726 "Build mediastreamer2 with the G726 codec." NO) option(ENABLE_GSM "Build mediastreamer2 with the GSM codec." YES) option(ENABLE_BV16 "Build mediastreamer2 with the BV16 codec." YES) @@ -228,6 +229,12 @@ endif() if(ENABLE_PORTAUDIO) find_package(PortAudio REQUIRED) endif() +if(ENABLE_SNDIO) + find_library(SNDIO NAMES sndio) + if (SNDIO) + set(SNDIO_FOUND ON) + endif() +endif() if(ENABLE_PULSEAUDIO) find_package(PulseAudio) if(NOT PULSEAUDIO_FOUND) @@ -348,7 +355,7 @@ if(ENABLE_QNX) endif() endif() -if(ENABLE_SOUND AND NOT(WIN32 OR ENABLE_ALSA OR ENABLE_ARTSC OR ENABLE_MACSND OR ENABLE_MACAQSND OR ENABLE_OSS OR ENABLE_PORTAUDIO OR ENABLE_PULSEAUDIO OR ENABLE_QSA OR ENABLE_ANDROIDSND)) +if(ENABLE_SOUND AND NOT(WIN32 OR ENABLE_ALSA OR ENABLE_ARTSC OR ENABLE_MACSND OR ENABLE_MACAQSND OR ENABLE_OSS OR ENABLE_PORTAUDIO OR ENABLE_PULSEAUDIO OR ENABLE_SNDIO OR ENABLE_QSA OR ENABLE_ANDROIDSND)) message(FATAL_ERROR "Could not find a support sound driver API. Use -DENABLE_SOUND=NO if you don't care about having sound.") endif() @@ -442,6 +449,10 @@ if(PULSEAUDIO_FOUND) list(APPEND MEDIASTREAMER2_INCLUDE_DIRS ${PULSEAUDIO_INCLUDE_DIRS}) set(__PULSEAUDIO_ENABLED__ 1) endif() +if(SNDIO_FOUND) + list(APPEND LINK_LIBS sndio) + set(__SNDIO_ENABLED__ 1) +endif() if(QSA_FOUND) list(APPEND LINK_LIBS ${QSA_LIBRARIES}) list(APPEND LINK_LIBS ${QNXAUDIOMANAGER_LIBRARIES}) diff --git a/include/mediastreamer2/allfilters.h b/include/mediastreamer2/allfilters.h index 81cabb66c4..bc62674499 100755 --- a/include/mediastreamer2/allfilters.h +++ b/include/mediastreamer2/allfilters.h @@ -32,6 +32,8 @@ typedef enum MSFilterId{ MS_ALSA_WRITE_ID, MS_OSS_READ_ID, MS_OSS_WRITE_ID, + MS_SNDIO_READ_ID, + MS_SNDIO_WRITE_ID, MS_ULAW_ENC_ID, MS_ULAW_DEC_ID, MS_ALAW_ENC_ID, diff --git a/mediastreamer-config.h.cmake b/mediastreamer-config.h.cmake index 7a3c3a8745..df00c4ff33 100644 --- a/mediastreamer-config.h.cmake +++ b/mediastreamer-config.h.cmake @@ -42,6 +42,7 @@ #cmakedefine __ALSA_ENABLED__ #cmakedefine __ARTS_ENABLED__ +#cmakedefine __SNDIO_ENABLED__ #cmakedefine __MACSND_ENABLED__ #cmakedefine __MAC_AQ_ENABLED__ #cmakedefine __PORTAUDIO_ENABLED__ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9cb8c71c36..2d6f192d70 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -177,6 +177,9 @@ endif() if(ENABLE_PULSEAUDIO) list(APPEND VOIP_SOURCE_FILES_C audiofilters/pulseaudio.c) endif() +if(ENABLE_SNDIO) + list(APPEND VOIP_SOURCE_FILES_C audiofilters/sndio.c) +endif() if(ENABLE_QSA) list(APPEND VOIP_SOURCE_FILES_C audiofilters/qsa.c) endif() diff --git a/src/audiofilters/sndio.c b/src/audiofilters/sndio.c new file mode 100644 index 0000000000..5fbb102774 --- /dev/null +++ b/src/audiofilters/sndio.c @@ -0,0 +1,462 @@ +/* + * Copyright (c) 2021 Landry Breuil + * + * This file is part of mediastreamer2. + * + * 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 "mediastreamer2/mssndcard.h" +#include "mediastreamer2/msfilter.h" + +#include + +#include +#include +#include +#include +#include +#include + +MSFilter *ms_sndio_read_new(MSSndCard *card); +MSFilter *ms_sndio_write_new(MSSndCard *card); + + +typedef struct SndioData{ + struct sio_hdl *hdl; + ms_thread_t thread; + ms_mutex_t mutex; + queue_t rq; + MSBufferizer * bufferizer; + int rate; + int rchan; + int pchan; + bool_t read_started; + bool_t write_started; +} SndioData; + +static void sndio_set_level(MSSndCard *card, MSSndCardMixerElem e, int percent) +{ + ms_message("sndio_set_level(%d)", percent); + SndioData *d=(SndioData*)card->data; + /* set sndio volume from percent */ + if (!sio_setvol(d->hdl, percent * SIO_MAXVOL / 100)) + ms_message("sio_setvol failed"); +} + +static void sndio_onvol(void * data, unsigned val) +{ + *(int*)data = val * 100 / SIO_MAXVOL; + ms_message("onvol: sndio val=%u, returning %u", val, *(int*)data); +} + +static int sndio_get_level(MSSndCard *card, MSSndCardMixerElem e) +{ + ms_message("sndio_get_level"); + int level = SIO_MAXVOL; + SndioData *d=(SndioData*)card->data; + /* return sndio volume as a percent between 0 and SIO_MAXVOL (127) */ + if (!sio_onvol(d->hdl, sndio_onvol, &level)) + ms_message("no volume knob ?"); + return level; + +} + +static void sndio_set_source(MSSndCard *card, MSSndCardCapture source) +{ + ms_message("sndio_get_source"); +} + + +static void sndio_init(MSSndCard *card){ + ms_message("sndio_init"); + SndioData *d=ms_new0(SndioData,1); + d->hdl = sio_open(SIO_DEVANY, SIO_PLAY | SIO_REC, 0); + d->read_started=FALSE; + d->write_started=FALSE; + qinit(&d->rq); + d->bufferizer=ms_bufferizer_new(); + ms_mutex_init(&d->mutex,NULL); + card->data=d; +} + +static void sndio_uninit(MSSndCard *card){ + ms_message("sndio_uninit"); + SndioData *d=(SndioData*)card->data; + sio_close(d->hdl); + ms_bufferizer_destroy(d->bufferizer); + flushq(&d->rq,0); + ms_mutex_destroy(&d->mutex); + ms_free(d); +} + +static void sndio_unload(MSSndCardManager *m) +{ + ms_message("sndio_unload"); +} + +static void sndio_detect(MSSndCardManager *m); + +MSSndCardDesc sndio_card_desc={ + .driver_type="SNDIO", + .detect=sndio_detect, + .init=sndio_init, + .set_level=sndio_set_level, + .get_level=sndio_get_level, + .set_capture=sndio_set_source, + .create_reader=ms_sndio_read_new, + .create_writer=ms_sndio_write_new, + .uninit=sndio_uninit, + .unload=sndio_unload +}; + +static void sndio_detect(MSSndCardManager *m){ + ms_message("sndio_detect"); + MSSndCard *card = ms_snd_card_new(&sndio_card_desc); + card->name = ms_strdup("OpenBSD backend"); + card->device_type = MS_SND_CARD_DEVICE_TYPE_TELEPHONY; + card->capabilities = MS_SND_CARD_CAP_PLAYBACK|MS_SND_CARD_CAP_CAPTURE; + ms_snd_card_manager_prepend_card(m, card); +} + +static void * sndio_thread(void *p){ + ms_message("sndio_thread"); + MSSndCard *card=(MSSndCard*)p; + SndioData *d=(SndioData*)card->data; + uint8_t *wbuf = NULL, *rbuf = NULL; + mblk_t *rm = NULL; + size_t n; + size_t todo; + size_t rblksz, wblksz; + unsigned char *data; + struct sio_par par; + int prime; + + sio_initpar(&par); + + /* sndio doesn't support a-low and u-low */ + par.bits = 16; + par.bps = SIO_BPS(par.bits); + par.sig = 1; + par.le = SIO_LE_NATIVE; + + par.rchan = d->rchan; + par.pchan = d->pchan; + par.rate = d->rate; + ms_message("sndio: calling setpar with rchan=%d, pchan=%d, rate=%d", d->rchan, d->pchan, d->rate); + + if (!sio_setpar(d->hdl, &par)) { + ms_message("sio_setpar call failed"); + return NULL; + } + + if (!sio_getpar(d->hdl, &par)) { + ms_message("sio_getpar call failed"); + return NULL; + } + d->rchan = par.rchan; + d->pchan = par.pchan; + d->rate = par.rate; + ms_message("sndio: after setpar/getpar, rchan=%d, pchan=%d, rate=%d, round=%d, bufsz=%d, appbufsz=%d", d->rchan, d->pchan, d->rate, par.round, par.bufsz, par.appbufsz); + + wblksz = par.round * par.bps * par.pchan; + rblksz = par.round * par.bps * par.rchan; + wbuf = (uint8_t *)malloc(wblksz); + rbuf = (uint8_t *)malloc(rblksz); + + if (!sio_start(d->hdl)) { + ms_message("sndio: could not start"); + return NULL; + } + + ms_message("sndio_thread: started, wblksz=%zu, rblksz=%zu", wblksz, rblksz); + + /* + * device starts as soon as play buffer is full (bufsz sample + * written). Calling sio_read() before that (i.e. while device + * is still stopped) will deadlock. + */ + prime = par.bufsz / par.round; + + while (d->read_started || d->write_started) { + ms_message("sndio_thread: in mainloop, read_started=%d, write_started=%d", d->read_started, d->write_started); + + /* + * read and queue a block, but only if + * device has started (bufsz written) + */ + if (prime == 0) { + if (d->read_started) { + rm = allocb(rblksz, 0); + data = rm->b_wptr; + } else + data = rbuf; + + todo = rblksz; + while (todo > 0) { + ms_message("sndio_thread: todo=%zu", todo); + n = sio_read(d->hdl, data, todo); + if (n == 0) { + ms_message("sndio_thread: sio_read error"); + goto exit_thread; + } + ms_message("sndio_thread: read %zu blocks from sndio", n); + data += n; + todo -= n; + } + if (d->read_started) { + rm->b_wptr += rblksz; + ms_message("sndio_thread: read started, queuing block (queue has %d items)", d->rq.q_mcount); + ms_mutex_lock(&d->mutex); + putq(&d->rq, rm); + ms_mutex_unlock(&d->mutex); + rm = NULL; + } else + ms_message("sndio_thread: read not started, dropping block"); + } else { + ms_message("sndio_thread: prime = %d, skipped block", prime); + prime--; + } + + if (d->write_started) { + n = ms_bufferizer_read(d->bufferizer, wbuf, wblksz); + if (n > 0) { + ms_message("sndio_thread: got %zu blocks from bufferizer", n); + } else + ms_message("sndio_thread: bufferizer empty ?"); + } else { + n = 0; + ms_message("sndio_thread: write not started, zeroing wbuf"); + } + + /* fill with silence remaining buffer */ + memset(wbuf + n, 0, wblksz - n); + + n = sio_write(d->hdl, wbuf, wblksz); + ms_message("sndio_thread: wrote %zu blocks to sndio", n); + } + +exit_thread: + ms_message("sndio_thread: stopping"); + if (!sio_stop(d->hdl)) { + ms_message("sndio: could not stop"); + return NULL; + } + + if (rm != NULL) { + freeb(rm); + rm = NULL; + } + + free(wbuf); + free(rbuf); + + return NULL; +} + +static mblk_t *sndio_get(MSSndCard *card){ + SndioData *d=(SndioData*)card->data; + if (d->rq.q_mcount > 0) + ms_message("sndio_get (queue has %d items)", d->rq.q_mcount); + mblk_t *m; + ms_mutex_lock(&d->mutex); + m=getq(&d->rq); + ms_mutex_unlock(&d->mutex); + return m; +} + +static void sndio_put(MSSndCard *card, mblk_t *m){ + ms_message("sndio_put"); + SndioData *d=(SndioData*)card->data; + ms_mutex_lock(&d->mutex); + ms_bufferizer_put(d->bufferizer,m); + ms_mutex_unlock(&d->mutex); +} + + +static void sndio_read_preprocess(MSFilter *f){ + ms_message("sndio_read_preprocess"); + MSSndCard *card=(MSSndCard*)f->data; + SndioData *d=(SndioData*)card->data; + if (d->read_started==FALSE && d->write_started==FALSE){ + d->read_started=TRUE; + ms_thread_create(&d->thread,NULL,sndio_thread,card); + }else d->read_started=TRUE; +} + +static void sndio_read_postprocess(MSFilter *f){ + ms_message("sndio_read_postprocess"); + MSSndCard *card=(MSSndCard*)f->data; + SndioData *d=(SndioData*)card->data; + d->read_started=FALSE; + if (d->write_started==FALSE){ + ms_message("sndio_read_postprocess: waiting for thread exit"); + ms_thread_join(d->thread,NULL); + } +} + +static void sndio_read_process(MSFilter *f){ + //ms_message("sndio_read_process"); + MSSndCard *card=(MSSndCard*)f->data; + /* defined in ortp/str_utils.h */ + mblk_t *m; + while((m=sndio_get(card))!=NULL){ + ms_queue_put(f->outputs[0],m); + } +} + +static void sndio_write_preprocess(MSFilter *f){ + ms_message("sndio_write_preprocess"); + MSSndCard *card=(MSSndCard*)f->data; + SndioData *d=(SndioData*)card->data; + if (d->read_started==FALSE && d->write_started==FALSE){ + d->write_started=TRUE; + ms_thread_create(&d->thread,NULL,sndio_thread,card); + }else{ + d->write_started=TRUE; + } +} + +static void sndio_write_postprocess(MSFilter *f){ + ms_message("sndio_write_postprocess"); + MSSndCard *card=(MSSndCard*)f->data; + SndioData *d=(SndioData*)card->data; + d->write_started=FALSE; + if (d->read_started==FALSE){ + ms_message("sndio_write_postprocess: waiting for thread exit"); + ms_thread_join(d->thread,NULL); + } +} + +static void sndio_write_process(MSFilter *f){ + ms_message("sndio_write_process"); + MSSndCard *card=(MSSndCard*)f->data; + mblk_t *m; + while((m=ms_queue_get(f->inputs[0]))!=NULL){ + sndio_put(card,m); + } +} + +static int sndio_set_rate(MSFilter *f, void *arg){ + MSSndCard *card=(MSSndCard*)f->data; + SndioData *d=(SndioData*)card->data; + d->rate=*((int*)arg); + ms_message("sndio_set_rate(%d)", d->rate); + return 0; +} + +static int sndio_get_rate(MSFilter *f, void *arg){ + MSSndCard *card=(MSSndCard*)f->data; + SndioData *d=(SndioData*)card->data; + ms_message("sndio_get_rate() returning %d", d->rate); + /* set arg with rate */ + *((int*)arg)=d->rate; + return 0; +} + +static int sndio_set_play_nchannels(MSFilter *f, void *arg){ + MSSndCard *card=(MSSndCard*)f->data; + SndioData *d=(SndioData*)card->data; + /* set sndio pchan from arg */ + d->pchan=*((int*)arg); + ms_message("sndio_set_play_nchannels(%d)", d->pchan); + return 0; +} + +static int sndio_get_play_nchannels(MSFilter *f, void *arg){ + MSSndCard *card=(MSSndCard*)f->data; + SndioData *d=(SndioData*)card->data; + ms_message("sndio_get_play_nchannels() returning %d", d->pchan); + /* set arg with sndio pchan, should actually call sio_getpar ? */ + *((int*)arg)=d->pchan; + return 0; +} + +static int sndio_set_record_nchannels(MSFilter *f, void *arg){ + MSSndCard *card=(MSSndCard*)f->data; + SndioData *d=(SndioData*)card->data; + /* set sndio rchan from arg */ + d->rchan=*((int*)arg); + ms_message("sndio_set_record_nchannels(%d)", d->rchan); + return 0; +} + +static int sndio_get_record_nchannels(MSFilter *f, void *arg){ + MSSndCard *card=(MSSndCard*)f->data; + SndioData *d=(SndioData*)card->data; + ms_message("sndio_get_record_nchannels() returning %d", d->rchan); + /* set arg with sndio rchan */ + *((int*)arg)=d->rchan; + return 0; +} + +static MSFilterMethod sndio_read_methods[]={ + { MS_FILTER_SET_SAMPLE_RATE , sndio_set_rate }, + { MS_FILTER_GET_SAMPLE_RATE , sndio_get_rate }, + { MS_FILTER_SET_NCHANNELS , sndio_set_record_nchannels }, + { MS_FILTER_GET_NCHANNELS , sndio_get_record_nchannels }, + { 0 , NULL } +}; + +static MSFilterMethod sndio_write_methods[]={ + { MS_FILTER_SET_SAMPLE_RATE , sndio_set_rate }, + { MS_FILTER_GET_SAMPLE_RATE , sndio_get_rate }, + { MS_FILTER_SET_NCHANNELS , sndio_set_play_nchannels }, + { MS_FILTER_GET_NCHANNELS , sndio_get_play_nchannels }, + { 0 , NULL } +}; +MSFilterDesc sndio_read_desc={ + .id=MS_SNDIO_READ_ID, + .name="MSSndioRead", + .text="Sound capture filter for SNDIO drivers", + .category=MS_FILTER_OTHER, + .ninputs=0, + .noutputs=1, + .preprocess=sndio_read_preprocess, + .process=sndio_read_process, + .postprocess=sndio_read_postprocess, + .methods=sndio_read_methods +}; + + +MSFilterDesc sndio_write_desc={ + .id=MS_SNDIO_WRITE_ID, + .name="MSSndioWrite", + .text="Sound playback filter for SNDIO drivers", + .category=MS_FILTER_OTHER, + .ninputs=1, + .noutputs=0, + .preprocess=sndio_write_preprocess, + .process=sndio_write_process, + .postprocess=sndio_write_postprocess, + .methods=sndio_write_methods +}; + +MSFilter *ms_sndio_read_new(MSSndCard *card){ + ms_message("sndio_read_new"); + MSFilter *f=ms_factory_create_filter_from_desc(ms_snd_card_get_factory(card),&sndio_read_desc); + f->data=card; + return f; +} + + +MSFilter *ms_sndio_write_new(MSSndCard *card){ + ms_message("sndio_write_new"); + MSFilter *f=ms_factory_create_filter_from_desc(ms_snd_card_get_factory(card),&sndio_write_desc); + f->data=card; + return f; +} + +MS_FILTER_DESC_EXPORT(sndio_read_desc) +MS_FILTER_DESC_EXPORT(sndio_write_desc) diff --git a/src/voip/msvoip.c b/src/voip/msvoip.c index a141dcaa97..d02389146c 100644 --- a/src/voip/msvoip.c +++ b/src/voip/msvoip.c @@ -104,6 +104,10 @@ extern MSSndCardDesc aq_card_desc; extern MSSndCardDesc pulse_card_desc; #endif +#ifdef __SNDIO_ENABLED__ +extern MSSndCardDesc sndio_card_desc; +#endif + #if TARGET_OS_IPHONE extern MSSndCardDesc au_card_desc; #endif @@ -123,6 +127,10 @@ static MSSndCardDesc * ms_snd_card_descs[]={ &pulse_card_desc, #endif +#ifdef __SNDIO_ENABLED__ + &sndio_card_desc, +#endif + #ifdef __ALSA_ENABLED__ &alsa_card_desc, #endif