From 1dd1509a02fee564ff87f80c2f29055d7aad889c Mon Sep 17 00:00:00 2001 From: Fulup Ar Foll Date: Fri, 11 May 2018 01:18:43 +0200 Subject: Initial version with softvol control and DMIX --- conf.d/project/lua.d/softmixer-01.lua | 4 +- nbproject/configurations.xml | 13 +- plugins/alsa/alsa-capture.c | 80 ------- plugins/alsa/alsa-core-ctl.c | 425 ++++++++++++++++++++++++++++++++++ plugins/alsa/alsa-core-pcm.c | 357 ++++++++++++++++++++++++++++ plugins/alsa/alsa-ctl.c | 399 ------------------------------- plugins/alsa/alsa-dmix.c | 74 ------ plugins/alsa/alsa-pcm.c | 380 ------------------------------ plugins/alsa/alsa-plug-dmix.c | 76 ++++++ plugins/alsa/alsa-plug-vol.c | 77 ++++++ plugins/alsa/alsa-softmixer.c | 95 ++++---- plugins/alsa/alsa-softmixer.h | 84 ++++--- plugins/alsa/alsa-utils-bypath.c | 75 +++--- plugins/alsa/alsa-utils-dump.c | 2 +- 14 files changed, 1087 insertions(+), 1054 deletions(-) delete mode 100644 plugins/alsa/alsa-capture.c create mode 100644 plugins/alsa/alsa-core-ctl.c create mode 100644 plugins/alsa/alsa-core-pcm.c delete mode 100644 plugins/alsa/alsa-ctl.c delete mode 100644 plugins/alsa/alsa-dmix.c delete mode 100644 plugins/alsa/alsa-pcm.c create mode 100644 plugins/alsa/alsa-plug-dmix.c create mode 100644 plugins/alsa/alsa-plug-vol.c diff --git a/conf.d/project/lua.d/softmixer-01.lua b/conf.d/project/lua.d/softmixer-01.lua index 8d7aae9..8858b93 100644 --- a/conf.d/project/lua.d/softmixer-01.lua +++ b/conf.d/project/lua.d/softmixer-01.lua @@ -41,12 +41,14 @@ function _mixer_config_ (source, args) local devout = { ["path"]= "/dev/snd/by-id/usb-YAMAHA_Corporation_YAMAHA_AP-U70_USB_Audio_00-00", + -- ["path"]= "/dev/snd/by-id/usb-0b0e_Jabra_SOLEMATE_v1.34.0-00", ["dev"]= 0, ["sub"]= 0, } local params = { - ["rate"]= 44100, + ["rate"]= 48000, + --["rate"]= 44100, ["channels"]= 2, } diff --git a/nbproject/configurations.xml b/nbproject/configurations.xml index 1519d80..95c71af 100644 --- a/nbproject/configurations.xml +++ b/nbproject/configurations.xml @@ -24,9 +24,9 @@ alsa-capture.c - alsa-ctl.c - alsa-dmix.c - alsa-pcm.c + alsa-core-ctl.c + alsa-core-pcm.c + alsa-plug-vol.c alsa-softmixer.c alsa-utils-bypath.c alsa-utils-dump.c @@ -169,6 +169,7 @@ ../../../opt/include/afb mixer-binding + ../../../opt/include/alsa app-afb-helpers-submodule app-controller-submodule/ctl-lib ../../../opt/include @@ -209,15 +210,15 @@ - + - + - + diff --git a/plugins/alsa/alsa-capture.c b/plugins/alsa/alsa-capture.c deleted file mode 100644 index 992f731..0000000 --- a/plugins/alsa/alsa-capture.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2018 "IoT.bzh" - * Author Fulup Ar Foll - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#define _GNU_SOURCE // needed for vasprintf - -#include "alsa-softmixer.h" - -PUBLIC snd_pcm_t* AlsaCreateCapture(CtlSourceT *source, const char* sndDevPath, unsigned int deviceIdx, unsigned int subpcmNamex, unsigned int channelCount) { - char pcmName[32]; - int error; - snd_pcm_t *pcmHandle; - snd_pcm_hw_params_t *pcmParams; - - AFB_ApiNotice(source->api, "AlsaCreateCapture: start "); - - // get card info from /dev/snd/xxx - snd_ctl_card_info_t *cardInfo = AlsaByPathInfo(source, sndDevPath); - - if (!cardInfo) { - AFB_ApiError(source->api,"AlsaCreateCapture: fail to find sndcard by path= %s", sndDevPath); - goto OnErrorExit; - } - - // extract useful info from cardInfo handle - int cardIdx = snd_ctl_card_info_get_card(cardInfo); - const char *cardName = snd_ctl_card_info_get_name(cardInfo); - - - // build a valid name and open sndcard - snprintf(pcmName, sizeof (pcmName), "hw:%i,%i,%i", cardIdx, deviceIdx, subpcmNamex); - error= snd_pcm_open(&pcmHandle, pcmName, SND_PCM_STREAM_CAPTURE, SND_PCM_ASYNC); - if (error) { - AFB_ApiError(source->api,"AlsaCreateCapture: fail openpcm (hw:%d -> %s pcmNamex=%i subpcmNamex=%d): %s" - , cardIdx, cardName, deviceIdx, subpcmNamex, snd_strerror(error)); - goto OnErrorExit; - } - - // set default to param object - snd_pcm_hw_params_alloca(&pcmParams); - snd_pcm_hw_params_any(pcmHandle, pcmParams); - - - error= snd_pcm_hw_params_set_channels (pcmHandle, pcmParams, channelCount); - if (error) { - AFB_ApiError(source->api,"lsaCreateCapture: fail set channel count (hw:%d -> %s pcmNamex=%i subpcmNamex=%d channelCount=%d): %s" - , cardIdx, cardName, deviceIdx, subpcmNamex, channelCount, snd_strerror(error)); - goto OnErrorExit; - } - - // push selected params to PCM - error= snd_pcm_hw_params(pcmHandle, pcmParams); - if (error) { - AFB_ApiError(source->api,"lsaCreateCapture: fail pushing params (hw:%d -> %s pcmNamex=%i subpcmNamex=%d): %s" - , cardIdx, cardName, deviceIdx, subpcmNamex, snd_strerror(error)); - goto OnErrorExit; - } - - AFB_ApiNotice(source->api, "AlsaCreateCapture: done"); - free (cardInfo); - return NULL; - -OnErrorExit: - AFB_ApiNotice(source->api, "AlsaCreateCapture: OnErrorExit"); - return NULL; -} \ No newline at end of file diff --git a/plugins/alsa/alsa-core-ctl.c b/plugins/alsa/alsa-core-ctl.c new file mode 100644 index 0000000..5944fb8 --- /dev/null +++ b/plugins/alsa/alsa-core-ctl.c @@ -0,0 +1,425 @@ +/* + * Copyright(C) 2018 "IoT.bzh" + * Author Fulup Ar Foll + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http : //www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License + +for the specific language governing permissions and + * limitations under the License. + * + * reference : + * https://github.com/zonque/simple-alsa-loop/blob/master/loop.c + * https://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html#a31 + * + */ + +#define _GNU_SOURCE // needed for vasprintf + +#include "alsa-softmixer.h" +#include +#include + +typedef struct { + AFB_ApiT api; + sd_event_source* evtsrc; + pthread_t thread; + int tid; + char* info; + snd_ctl_t *ctlDev; + sd_event *sdLoop; +} SubscribeHandleT; + +typedef struct { + AlsaPcmInfoT *pcm; + int numid; +} SubStreamT; + +typedef struct { + SubStreamT stream[MAX_AUDIO_STREAMS + 1]; + int count; +} AudioStreamHandleT; + +static AudioStreamHandleT AudioStreamHandle; + +STATIC snd_ctl_elem_id_t *AlsaCtlGetElemId(CtlSourceT *source, snd_ctl_t* ctlDev, int numid) { + char string[32]; + int error; + int index; + snd_ctl_elem_list_t *ctlList = NULL; + snd_ctl_elem_id_t *elemId; + + snd_ctl_elem_list_alloca(&ctlList); + + if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { + AFB_ApiError(source->api, "AlsaCtlElemIdGetInt [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string)); + goto OnErrorExit; + } + + if ((error = snd_ctl_elem_list_alloc_space(ctlList, snd_ctl_elem_list_get_count(ctlList))) < 0) { + AFB_ApiError(source->api, "AlsaCtlElemIdGetInt [%s] fail retrieve count", ALSA_CTL_UID(ctlDev, string)); + goto OnErrorExit; + } + + // Fulup: do not understand why snd_ctl_elem_list should be call twice to get a valid ctlCount + if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { + AFB_ApiError(source->api, "AlsaCtlElemIdGetInt [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string)); + goto OnErrorExit; + } + + // loop on control to find the right one + int ctlCount = snd_ctl_elem_list_get_used(ctlList); + for (index = 0; index < ctlCount; index++) { + + if (numid == snd_ctl_elem_list_get_numid(ctlList, index)) { + snd_ctl_elem_id_malloc(&elemId); + snd_ctl_elem_list_get_id(ctlList, index, elemId); + break; + } + } + + if (index == ctlCount) { + AFB_ApiError(source->api, "AlsaCtlRegister [%s] fail get numid=%i count", ALSA_CTL_UID(ctlDev, string), numid); + goto OnErrorExit; + } + + // clear ctl list and return elemid + snd_ctl_elem_list_clear(ctlList); + return elemId; + +OnErrorExit: + if (ctlList) snd_ctl_elem_list_clear(ctlList); + return NULL; +} + + +PUBLIC snd_ctl_t *AlsaCtlOpenCtl(CtlSourceT *source, const char *devid) { + int error; + snd_ctl_t *ctlDev; + + if (devid) goto OnErrorExit; + + if ((error = snd_ctl_open(&ctlDev, devid, SND_CTL_READONLY)) < 0) { + devid = "Not Defined"; + goto OnErrorExit; + } + + return ctlDev; + +OnErrorExit: + AFB_ApiError(source->api, "AlsaCtlOpenCtl: fail to find sndcard by id= %s", devid); + return NULL; +} + +STATIC int CtlElemIdGetNumid(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, int *numid) { + snd_ctl_elem_info_t *elemInfo; + + snd_ctl_elem_info_alloca(&elemInfo); + snd_ctl_elem_info_set_id(elemInfo, elemId); + if (snd_ctl_elem_info(ctlDev, elemInfo) < 0) goto OnErrorExit; + if (!snd_ctl_elem_info_is_readable(elemInfo)) goto OnErrorExit; + + *numid = snd_ctl_elem_info_get_numid(elemInfo); + + return 0; + +OnErrorExit: + return -1; +} + +STATIC int CtlElemIdGetInt(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, long *value) { + int error; + snd_ctl_elem_value_t *elemData; + snd_ctl_elem_info_t *elemInfo; + + snd_ctl_elem_info_alloca(&elemInfo); + snd_ctl_elem_info_set_id(elemInfo, elemId); + if (snd_ctl_elem_info(ctlDev, elemInfo) < 0) goto OnErrorExit; + if (!snd_ctl_elem_info_is_readable(elemInfo)) goto OnErrorExit; + + // as we have static rate/channel we should have only one boolean as value + snd_ctl_elem_type_t elemType = snd_ctl_elem_info_get_type(elemInfo); + int count = snd_ctl_elem_info_get_count(elemInfo); + if (count != 1) goto OnErrorExit; + + snd_ctl_elem_value_alloca(&elemData); + snd_ctl_elem_value_set_id(elemData, elemId); + error = snd_ctl_elem_read(ctlDev, elemData); + if (error) goto OnSuccessExit; + + // value=1 when active and 0 when not active + *value = snd_ctl_elem_value_get_integer(elemData, 0); + +OnSuccessExit: + return 0; + +OnErrorExit: + + AFB_ApiWarning(api, "CtlSubscribeEventCB: ignored unsupported event Numid=%i", snd_ctl_elem_info_get_numid(elemInfo)); + for (int idx = 0; idx < count; idx++) { + long valueL; + + switch (elemType) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + valueL = snd_ctl_elem_value_get_boolean(elemData, idx); + AFB_ApiNotice(api, "CtlElemIdGetBool: value=%ld", valueL); + break; + case SND_CTL_ELEM_TYPE_INTEGER: + valueL = snd_ctl_elem_value_get_integer(elemData, idx); + AFB_ApiNotice(api, "CtlElemIdGetInt: value=%ld", valueL); + break; + case SND_CTL_ELEM_TYPE_INTEGER64: + valueL = snd_ctl_elem_value_get_integer64(elemData, idx); + AFB_ApiNotice(api, "CtlElemIdGetInt64: value=%ld", valueL); + break; + case SND_CTL_ELEM_TYPE_ENUMERATED: + valueL = snd_ctl_elem_value_get_enumerated(elemData, idx); + AFB_ApiNotice(api, "CtlElemIdGetEnum: value=%ld", valueL); + break; + case SND_CTL_ELEM_TYPE_BYTES: + valueL = snd_ctl_elem_value_get_byte(elemData, idx); + AFB_ApiNotice(api, "CtlElemIdGetByte: value=%ld", valueL); + break; + case SND_CTL_ELEM_TYPE_IEC958: + default: + AFB_ApiNotice(api, "CtlElemIdGetInt: Unsupported type=%d", elemType); + break; + } + } + return -1; +} + +// Clone of AlsaLib snd_card_load2 static function +PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(CtlSourceT *source, const char *devid) { + int error; + snd_ctl_t *ctlDev; + + if (devid) goto OnErrorExit; + + if ((error = snd_ctl_open(&ctlDev, devid, SND_CTL_READONLY)) < 0) { + devid = "Not Defined"; + goto OnErrorExit; + } + + snd_ctl_card_info_t *cardInfo = malloc(snd_ctl_card_info_sizeof()); + if ((error = snd_ctl_card_info(ctlDev, cardInfo)) < 0) { + goto OnErrorExit; + } + return cardInfo; + +OnErrorExit: + AFB_ApiError(source->api, "AlsaCtlGetInfo: fail to find sndcard by id= %s", devid); + return NULL; +} + +PUBLIC int AlsaCtlGetNumidValueI(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long* value) { + + snd_ctl_elem_id_t *elemId = AlsaCtlGetElemId(source, ctlDev, numid); + if (!elemId) { + AFB_ApiError(source->api, "AlsaCtlGetNumValueI [sndcard=%s] fail to find numid=%d", snd_ctl_name(ctlDev), numid); + goto OnErrorExit; + } + + int error = CtlElemIdGetInt(source->api, ctlDev, elemId, value); + if (error) { + AFB_ApiError(source->api, "AlsaCtlGetNumValueI [sndcard=%s] fail to get numid=%d value", snd_ctl_name(ctlDev), numid); + goto OnErrorExit; + } + + return 0; +OnErrorExit: + return -1; +} + +STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData) { + int error, numid; + SubscribeHandleT *subscribeHandle = (SubscribeHandleT*) userData; + snd_ctl_event_t *eventId; + snd_ctl_elem_id_t *elemId; + long value; + + if ((revents & EPOLLHUP) != 0) { + AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB hanghup [card:%s disconnected]", subscribeHandle->info); + goto OnSuccessExit; + } + + if ((revents & EPOLLIN) == 0) goto OnSuccessExit; + + // initialise event structure on stack + snd_ctl_event_alloca(&eventId); + snd_ctl_elem_id_alloca(&elemId); + + error = snd_ctl_read(subscribeHandle->ctlDev, eventId); + if (error < 0) goto OnErrorExit; + + // we only process sndctrl element + if (snd_ctl_event_get_type(eventId) != SND_CTL_EVENT_ELEM) goto OnSuccessExit; + + // we only process value changed events + unsigned int eventMask = snd_ctl_event_elem_get_mask(eventId); + if (!(eventMask & SND_CTL_EVENT_MASK_VALUE)) goto OnSuccessExit; + + // extract element from event and get value + snd_ctl_event_elem_get_id(eventId, elemId); + error = CtlElemIdGetInt(subscribeHandle->api, subscribeHandle->ctlDev, elemId, &value); + if (error) goto OnErrorExit; + + error = CtlElemIdGetNumid(subscribeHandle->api, subscribeHandle->ctlDev, elemId, &numid); + if (error) goto OnErrorExit; + + for (int idx = 0; idx < AudioStreamHandle.count; idx++) { + if (AudioStreamHandle.stream[idx].numid == numid) { + const char *pcmName = AudioStreamHandle.stream[idx].pcm->devid; + snd_pcm_pause(AudioStreamHandle.stream[idx].pcm->handle, !value); + AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d pcm=%s pause=%d numid=%d", subscribeHandle->info, subscribeHandle->tid, pcmName, !value, numid); + break; + } + } + +OnSuccessExit: + return 0; + +OnErrorExit: + AFB_ApiWarning(subscribeHandle->api, "CtlSubscribeEventCB: ignored unsupported event"); + return 0; +} + +static void *LoopInThread(void *handle) { + SubscribeHandleT *subscribeHandle = (SubscribeHandleT*) handle; + int count = 0; + int watchdog = MAINLOOP_WATCHDOG * 1000; + subscribeHandle->tid = (int) syscall(SYS_gettid); + + AFB_ApiNotice(subscribeHandle->api, "LoopInThread:%s/%d Started", subscribeHandle->info, subscribeHandle->tid); + + /* loop until end */ + for (;;) { + int res = sd_event_run(subscribeHandle->sdLoop, watchdog); + if (res == 0) { + AFB_ApiInfo(subscribeHandle->api, "LoopInThread:%s/%d Idle count=%d", subscribeHandle->info, subscribeHandle->tid, count++); + continue; + } + if (res < 0) { + AFB_ApiError(subscribeHandle->api, "LoopInThread:%s/%d ERROR=%i Exit errno=%s", subscribeHandle->info, subscribeHandle->tid, res, strerror(res)); + break; + } + } + pthread_exit(0); +} + +PUBLIC snd_ctl_t* AlsaCrlFromPcm(CtlSourceT *source, snd_pcm_t *pcm) { + char buffer[32]; + int error; + snd_ctl_t *ctlDev; + snd_pcm_info_t *pcmInfo; + + snd_pcm_info_alloca(&pcmInfo); + if ((error = snd_pcm_info(pcm, pcmInfo)) < 0) goto OnErrorExit; + + int pcmCard = snd_pcm_info_get_card(pcmInfo); + snprintf(buffer, sizeof (buffer), "hw:%i", pcmCard); + if ((error = snd_ctl_open(&ctlDev, buffer, SND_CTL_READONLY)) < 0) goto OnErrorExit; + + return ctlDev; + +OnErrorExit: + return NULL; +} + + +PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t * ctlDev) { + int error; + char string [32]; + struct pollfd pfds; + SubscribeHandleT *subscribeHandle = malloc(sizeof (SubscribeHandleT)); + + subscribeHandle->api = source->api; + subscribeHandle->ctlDev = ctlDev; + subscribeHandle->info = "ctlEvt"; + + // subscribe for sndctl events attached to devid + if ((error = snd_ctl_subscribe_events(ctlDev, 1)) < 0) { + AFB_ApiError(source->api, "AlsaCtlSubscribe: fail sndcard=%s to subscribe events", ALSA_CTL_UID(ctlDev, string)); + goto OnErrorExit; + } + + // get pollfd attach to this sound board + int count = snd_ctl_poll_descriptors(ctlDev, &pfds, 1); + if (count != 1) { + AFB_ApiError(source->api, "AlsaCtlSubscribe: fail sndcard=%s get poll descriptors", ALSA_CTL_UID(ctlDev, string)); + goto OnErrorExit; + } + + // add poll descriptor to AGL systemd mainloop + if ((error = sd_event_new(&subscribeHandle->sdLoop)) < 0) { + fprintf(stderr, "AlsaCtlSubscribe: fail sndcard=%s creating a new loop", ALSA_CTL_UID(ctlDev, string)); + goto OnErrorExit; + } + + // register sound event to binder main loop + if ((error = sd_event_add_io(subscribeHandle->sdLoop, &subscribeHandle->evtsrc, pfds.fd, EPOLLIN, CtlSubscribeEventCB, subscribeHandle)) < 0) { + AFB_ApiError(source->api, "AlsaCtlSubscribe: Fail sndcard=%s adding mainloop", ALSA_CTL_UID(ctlDev, string)); + goto OnErrorExit; + } + + // start a thread with a mainloop to monitor Audio-Agent + if ((error = pthread_create(&subscribeHandle->thread, NULL, &LoopInThread, subscribeHandle)) < 0) { + AFB_ApiError(source->api, "AlsaCtlSubscribe: Fail sndcard=%s create waiting thread err=%d", ALSA_CTL_UID(ctlDev, string), error); + goto OnErrorExit; + } + + return 0; + +OnErrorExit: + return -1; +} + +PUBLIC int AlsaCtlRegister(CtlSourceT *source, AlsaPcmInfoT *pcm, int numid) { + long value; + int error; + + // NumID are attached to sndcard retrieve ctldev from PCM + snd_ctl_t* ctlDev = AlsaCrlFromPcm(source, pcm->handle); + if (!ctlDev) { + AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail attache sndcard", pcm->devid); + goto OnErrorExit; + } + + // This is the first registration let's subscrive to event + if (AudioStreamHandle.count == 0) { + AlsaCtlSubscribe(source, ctlDev); + } + + error = AlsaCtlGetNumidValueI(source, ctlDev, numid, &value); + if (error) goto OnErrorExit; + + AFB_ApiNotice(source->api, "AlsaCtlRegister [pcm=%s] numid=%d value=%ld", pcm->devid, numid, value); + + // store PCM in order to pause/resume depending on event + int count=AudioStreamHandle.count; + AudioStreamHandle.stream[count].pcm = pcm; + AudioStreamHandle.stream[count].numid = numid; + + // we only need to keep ctldev open for initial registration + if (AudioStreamHandle.count++ > 0) snd_ctl_close(ctlDev); + + // toggle pause/resume (should be done after pcm_start) + if ((error = snd_pcm_pause(pcm->handle, !value)) < 0) { + AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail to pause", pcm->devid); + goto OnErrorExit; + } + + return 0; + +OnErrorExit: + + return -1; +} diff --git a/plugins/alsa/alsa-core-pcm.c b/plugins/alsa/alsa-core-pcm.c new file mode 100644 index 0000000..a08dfca --- /dev/null +++ b/plugins/alsa/alsa-core-pcm.c @@ -0,0 +1,357 @@ +/* + * Copyright(C) 2018 "IoT.bzh" + * Author Fulup Ar Foll + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http : //www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License + +for the specific language governing permissions and + * limitations under the License. + * + * reference : + * https://github.com/zonque/simple-alsa-loop/blob/master/loop.c + * https://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html#a31 + * + */ + +#define _GNU_SOURCE // needed for vasprintf + +#include "alsa-softmixer.h" +#include +#include + + + +#define BUFFER_FRAME_COUNT 1024 + +typedef struct { + snd_pcm_t *pcmIn; + snd_pcm_t *pcmOut; + AFB_ApiT api; + sd_event_source* evtsrc; + void* buffer; + size_t frameSize; + unsigned int frameCount; + unsigned int channels; + sd_event *sdLoop; + pthread_t thread; + int tid; + char* info; +} AlsaPcmCopyHandleT; + +STATIC int AlsaPeriodSize(snd_pcm_format_t pcmFormat) { + int pcmSampleSize; + + switch (pcmFormat) { + + case SND_PCM_FORMAT_S8: + case SND_PCM_FORMAT_U8: + pcmSampleSize = 1; + break; + + case SND_PCM_FORMAT_U16_LE: + case SND_PCM_FORMAT_U16_BE: + case SND_PCM_FORMAT_S16_LE: + case SND_PCM_FORMAT_S16_BE: + pcmSampleSize = 2; + break; + + case SND_PCM_FORMAT_U32_LE: + case SND_PCM_FORMAT_U32_BE: + case SND_PCM_FORMAT_S32_LE: + case SND_PCM_FORMAT_S32_BE: + pcmSampleSize = 4; + break; + + default: + pcmSampleSize = 0; + } + + return pcmSampleSize; +} + +PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *opts) { + char string[32]; + int error; + snd_pcm_hw_params_t *pxmHwParams; + snd_pcm_sw_params_t *pxmSwParams; + + // retrieve hadware config from PCM + snd_pcm_hw_params_alloca(&pxmHwParams); + snd_pcm_hw_params_any(pcm->handle, pxmHwParams); + + if (!opts->access) opts->access = SND_PCM_ACCESS_RW_INTERLEAVED; + error = snd_pcm_hw_params_set_access(pcm->handle, pxmHwParams, opts->access); + if (error) { + AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Interleave=%d mode error=%s", ALSA_PCM_UID(pcm->handle, string), opts->access, snd_strerror(error)); + goto OnErrorExit; + }; + + if (opts->format != SND_PCM_FORMAT_UNKNOWN) { + if ((error = snd_pcm_hw_params_set_format(pcm->handle, pxmHwParams, opts->format)) < 0) { + AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Format=%d error=%s", ALSA_PCM_UID(pcm->handle, string), opts->format, snd_strerror(error)); + AlsaDumpFormats(source, pcm->handle); + goto OnErrorExit; + } + } + + if (opts->rate > 0) { + unsigned int pcmRate = opts->rate; + if ((error = snd_pcm_hw_params_set_rate_near(pcm->handle, pxmHwParams, &opts->rate, 0)) < 0) { + AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Rate=%d error=%s", ALSA_PCM_UID(pcm->handle, string), opts->rate, snd_strerror(error)); + goto OnErrorExit; + } + + // check we got requested rate + if (opts->rate != pcmRate) { + AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Rate ask=%dHz get=%dHz", ALSA_PCM_UID(pcm->handle, string), pcmRate, opts->rate); + goto OnErrorExit; + } + } + + if (opts->channels) { + if ((error = snd_pcm_hw_params_set_channels(pcm->handle, pxmHwParams, opts->channels)) < 0) { + AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Channels=%d error=%s", ALSA_PCM_UID(pcm->handle, string), opts->channels, snd_strerror(error)); + goto OnErrorExit; + }; + } + + // store selected values + if ((error = snd_pcm_hw_params(pcm->handle, pxmHwParams)) < 0) { + AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s apply hwparams error=%s", ALSA_PCM_UID(pcm->handle, string), snd_strerror(error)); + goto OnErrorExit; + } + + // check we effective hw params after optional format change + snd_pcm_hw_params_get_channels(pxmHwParams, &opts->channels); + snd_pcm_hw_params_get_format(pxmHwParams, &opts->format); + snd_pcm_hw_params_get_rate(pxmHwParams, &opts->rate, 0); + opts->sampleSize = AlsaPeriodSize(opts->format); + if (opts->sampleSize == 0) { + AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s unsupported format format=%d", ALSA_PCM_UID(pcm->handle, string), opts->format); + goto OnErrorExit; + } + + // retrieve software config from PCM + snd_pcm_sw_params_alloca(&pxmSwParams); + snd_pcm_sw_params_current(pcm->handle, pxmSwParams); + + if ((error = snd_pcm_sw_params_set_avail_min(pcm->handle, pxmSwParams, 16)) < 0) { + AFB_ApiError(source->api, "AlsaPcmConf: Fail to PCM=%s set_buffersize error=%s", ALSA_PCM_UID(pcm->handle, string), snd_strerror(error)); + goto OnErrorExit; + }; + + // push software params into PCM + if ((error = snd_pcm_sw_params(pcm->handle, pxmSwParams)) < 0) { + AFB_ApiError(source->api, "AlsaPcmConf: Fail to push software=%s params error=%s", ALSA_PCM_UID(pcm->handle, string), snd_strerror(error)); + goto OnErrorExit; + }; + + AFB_ApiNotice(source->api, "AlsaPcmConf: PCM=%s channels=%d rate=%d format=%d access=%d done", ALSA_PCM_UID(pcm->handle,string), opts->channels, opts->rate, opts->format, opts->access); + return 0; + +OnErrorExit: + return -1; +} + +STATIC int AlsaPcmReadCB(sd_event_source* src, int fd, uint32_t revents, void* userData) { + char string[32]; + int error; + snd_pcm_sframes_t framesIn, framesOut, availIn, availOut; + AlsaPcmCopyHandleT *pcmCopyHandle = (AlsaPcmCopyHandleT*) userData; + + // PCM has was closed + if ((revents & EPOLLHUP) != 0) { + AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PCM=%s hanghup/disconnected", ALSA_PCM_UID(pcmCopyHandle->pcmIn, string)); + goto ExitOnSuccess; + } + + // ignore any non input events + if ((revents & EPOLLIN) == 0) { + goto ExitOnSuccess; + } + + // retrieve PCM state + snd_pcm_state_t pcmState = snd_pcm_state(pcmCopyHandle->pcmIn); + + // When pause flush remaining frame and wait + if (pcmState == SND_PCM_STATE_PAUSED) { + framesIn = snd_pcm_readi(pcmCopyHandle->pcmIn, pcmCopyHandle->buffer, pcmCopyHandle->frameCount); + AFB_ApiInfo(pcmCopyHandle->api, "AlsaPcmReadCB: paused frame:%ld ignored", framesIn); + goto ExitOnSuccess; + } + + // When XRNS append try to restore PCM + if (pcmState == SND_PCM_STATE_XRUN) { + AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PCM=%s XRUN", ALSA_PCM_UID(pcmCopyHandle->pcmIn, string)); + snd_pcm_prepare(pcmCopyHandle->pcmIn); + } + + // when PCM suspending loop until ready to go + if (pcmState == SND_PCM_STATE_SUSPENDED) { + while (1) { + if ((error = snd_pcm_resume(pcmCopyHandle->pcmIn)) < 0) { + AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PCM=%s SUSPENDED fail to resume", ALSA_PCM_UID(pcmCopyHandle->pcmIn, string)); + sleep(1); // Fulup should be replace with corresponding AFB_timer + } else { + AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PCM=%s SUSPENDED success to resume", ALSA_PCM_UID(pcmCopyHandle->pcmIn, string)); + } + } + } + + // do we have waiting frame + availIn = snd_pcm_avail_update(pcmCopyHandle->pcmIn); + if (availIn <= 0) { + goto ExitOnSuccess; + } + + // do we have space to push frame + availOut = snd_pcm_avail_update(pcmCopyHandle->pcmOut); + if (availOut <= 0) { + snd_pcm_prepare(pcmCopyHandle->pcmOut); + goto ExitOnSuccess; + } + + // make sure we can push all input frame into output pcm without locking + if (availOut < availIn) availIn = availOut; + + // we get too many data ignore some + if (availIn > pcmCopyHandle->frameCount) { + availIn = pcmCopyHandle->frameCount; + } + + // effectively read pcmIn and push frame to pcmOut + framesIn = snd_pcm_readi(pcmCopyHandle->pcmIn, pcmCopyHandle->buffer, availIn); + if (framesIn < 0 || framesIn != availIn) { + AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PcmIn=%s UNDERUN frame=%ld", ALSA_PCM_UID(pcmCopyHandle->pcmIn, string), framesIn); + goto ExitOnSuccess; + } + + // In/Out frames transfer through buffer copy + framesOut = snd_pcm_writei(pcmCopyHandle->pcmOut, pcmCopyHandle->buffer, framesIn); + if (framesOut < 0 || framesOut != framesIn) { + AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PcmOut=%s UNDERUN/SUSPEND frameOut=%ld", ALSA_PCM_UID(pcmCopyHandle->pcmOut, string), framesOut); + goto ExitOnSuccess; + } + + if (framesIn != framesOut) { + AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PCM=%s Loosing frames=%ld", ALSA_PCM_UID(pcmCopyHandle->pcmOut, string), (framesIn - framesOut)); + goto ExitOnSuccess; + } + + return 0; + + // Cannot handle error in callback +ExitOnSuccess: + return 0; +} + +static void *LoopInThread(void *handle) { + AlsaPcmCopyHandleT *pcmCopyHandle = (AlsaPcmCopyHandleT*) handle; + int count = 0; + int watchdog = MAINLOOP_WATCHDOG * 1000; + pcmCopyHandle->tid = (int) syscall(SYS_gettid); + + AFB_ApiNotice(pcmCopyHandle->api, "LoopInThread:%s/%d Started", pcmCopyHandle->info, pcmCopyHandle->tid); + + + /* loop until end */ + for (;;) { + int res = sd_event_run(pcmCopyHandle->sdLoop, watchdog); + if (res == 0) { + AFB_ApiInfo(pcmCopyHandle->api, "LoopInThread:%s/%d Idle count=%d", pcmCopyHandle->info, pcmCopyHandle->tid, count++); + continue; + } + if (res < 0) { + AFB_ApiError(pcmCopyHandle->api, "LoopInThread:%s/%d ERROR=%i Exit errno=%s.\n", pcmCopyHandle->info, pcmCopyHandle->tid, res, strerror(res)); + break; + } + } + pthread_exit(0); +} + +PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pcmOut, AlsaPcmHwInfoT * opts) { + char string[32]; + struct pollfd *pcmInFds; + int error; + + // prepare PCM for capture and replay + error = AlsaPcmConf(source, pcmIn, opts); + if (error) goto OnErrorExit; + + // Prepare PCM for usage + if ((error = snd_pcm_start(pcmIn->handle)) < 0) { + AFB_ApiError(source->api, "AlsaPcmCopy: Fail to prepare PCM=%s error=%s", ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); + goto OnErrorExit; + }; + + + error = AlsaPcmConf(source, pcmOut, opts); + if (error) goto OnErrorExit; + + // Prepare PCM for usage + if ((error = snd_pcm_prepare(pcmOut->handle)) < 0) { + AFB_ApiError(source->api, "AlsaPcmCopy: Fail to start PCM=%s error=%s", ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); + goto OnErrorExit; + }; + + + + AlsaPcmCopyHandleT *pcmCopyHandle = malloc(sizeof (AlsaPcmCopyHandleT)); + pcmCopyHandle->info = "pcmCpy"; + pcmCopyHandle->pcmIn = pcmIn->handle; + pcmCopyHandle->pcmOut = pcmOut->handle; + pcmCopyHandle->api = source->api; + pcmCopyHandle->channels = opts->channels; + pcmCopyHandle->frameSize = opts->channels * opts->sampleSize; + pcmCopyHandle->frameCount = BUFFER_FRAME_COUNT; + pcmCopyHandle->buffer = malloc(pcmCopyHandle->frameCount * pcmCopyHandle->frameSize); + + // get FD poll descriptor for capture PCM + int pcmInCount = snd_pcm_poll_descriptors_count(pcmCopyHandle->pcmIn); + if (pcmInCount <= 0) { + AFB_ApiError(source->api, "AlsaPcmCopy: Fail pcmIn=%s get fds count error=%s", ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); + goto OnErrorExit; + }; + + pcmInFds = alloca(sizeof (*pcmInFds) * pcmInCount); + if ((error = snd_pcm_poll_descriptors(pcmIn->handle, pcmInFds, pcmInCount)) < 0) { + AFB_ApiError(source->api, "AlsaPcmCopy: Fail pcmIn=%s get pollfds error=%s", ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); + goto OnErrorExit; + }; + + // add poll descriptor to AGL systemd mainloop + if ((error = sd_event_new(&pcmCopyHandle->sdLoop)) < 0) { + fprintf(stderr, "LaunchCallRequest: fail pcmin=%s creating a new loop: %s\n", ALSA_PCM_UID(pcmOut->handle, string), strerror(error)); + goto OnErrorExit; + } + + for (int idx = 0; idx < pcmInCount; idx++) { + if ((error = sd_event_add_io(pcmCopyHandle->sdLoop, &pcmCopyHandle->evtsrc, pcmInFds[idx].fd, EPOLLIN, AlsaPcmReadCB, pcmCopyHandle)) < 0) { + AFB_ApiError(source->api, "AlsaPcmCopy: Fail pcmIn=%s sd_event_add_io err=%d", ALSA_PCM_UID(pcmIn->handle, string), error); + goto OnErrorExit; + } + } + + // start a thread with a mainloop to monitor Audio-Agent + if ((error = pthread_create(&pcmCopyHandle->thread, NULL, &LoopInThread, pcmCopyHandle)) < 0) { + AFB_ApiError(source->api, "AlsaPcmCopy: Fail create waiting thread pcmIn=%s err=%d", ALSA_PCM_UID(pcmIn->handle, string), error); + goto OnErrorExit; + } + + return 0; + +OnErrorExit: + AFB_ApiError(source->api, "AlsaPcmCopy: Fail \n - pcmIn=%s \n - pcmOut=%s", ALSA_PCM_UID(pcmIn->handle, string), ALSA_PCM_UID(pcmOut->handle, string)); + + return -1; +} + diff --git a/plugins/alsa/alsa-ctl.c b/plugins/alsa/alsa-ctl.c deleted file mode 100644 index b469e94..0000000 --- a/plugins/alsa/alsa-ctl.c +++ /dev/null @@ -1,399 +0,0 @@ -/* - * Copyright(C) 2018 "IoT.bzh" - * Author Fulup Ar Foll - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http : //www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License - -for the specific language governing permissions and - * limitations under the License. - * - * reference : - * https://github.com/zonque/simple-alsa-loop/blob/master/loop.c - * https://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html#a31 - * - */ - -#define _GNU_SOURCE // needed for vasprintf - -#include "alsa-softmixer.h" -#include -#include - -typedef struct { - AFB_ApiT api; - sd_event_source* evtsrc; - pthread_t thread; - int tid; - char* info; - snd_ctl_t *ctlDev; - sd_event *sdLoop; -} SubscribeHandleT; - -typedef struct { - snd_pcm_t *pcm[MAX_AUDIO_STREAMS+1]; - int numid[MAX_AUDIO_STREAMS+1]; - int count; -} AudioStreamHandleT; - -static AudioStreamHandleT AudioStreamHandle; - -// Clone of AlsaLib snd_card_load2 static function - -snd_ctl_card_info_t *AlsaCtlGetInfo(CtlSourceT *source, const char *devid) { - int error; - snd_ctl_t *ctlDev; - - if (devid) goto OnErrorExit; - - if ((error = snd_ctl_open(&ctlDev, devid, SND_CTL_READONLY)) < 0) { - devid = "Not Defined"; - goto OnErrorExit; - } - - snd_ctl_card_info_t *cardInfo = malloc(snd_ctl_card_info_sizeof()); - if ((error = snd_ctl_card_info(ctlDev, cardInfo)) < 0) { - goto OnErrorExit; - } - return cardInfo; - -OnErrorExit: - AFB_ApiError(source->api, "AlsaCtlGetInfo: fail to find sndcard by id= %s", devid); - return NULL; -} - -PUBLIC snd_ctl_t *AlsaCtlOpenCtl(CtlSourceT *source, const char *devid) { - int error; - snd_ctl_t *ctlDev; - - if (devid) goto OnErrorExit; - - if ((error = snd_ctl_open(&ctlDev, devid, SND_CTL_READONLY)) < 0) { - devid = "Not Defined"; - goto OnErrorExit; - } - - return ctlDev; - -OnErrorExit: - AFB_ApiError(source->api, "AlsaCtlOpenCtl: fail to find sndcard by id= %s", devid); - return NULL; -} - -STATIC int CtlElemIdGetInt (AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, long *value) { - int error; - snd_ctl_elem_value_t *elemData; - snd_ctl_elem_info_t *elemInfo; - - snd_ctl_elem_info_alloca(&elemInfo); - snd_ctl_elem_info_set_id(elemInfo, elemId); - if (snd_ctl_elem_info(ctlDev, elemInfo) < 0) goto OnErrorExit; - if (!snd_ctl_elem_info_is_readable(elemInfo)) goto OnErrorExit; - - // as we have static rate/channel we should have only one boolean as value - snd_ctl_elem_type_t elemType = snd_ctl_elem_info_get_type(elemInfo); - int count = snd_ctl_elem_info_get_count(elemInfo); - if (count != 1) goto OnErrorExit; - - snd_ctl_elem_value_alloca(&elemData); - snd_ctl_elem_value_set_id(elemData, elemId); - error = snd_ctl_elem_read(ctlDev, elemData); - if (error) goto OnSuccessExit; - - // value=1 when active and 0 when not active - *value = snd_ctl_elem_value_get_integer(elemData, 0); - -OnSuccessExit: - return 0; - -OnErrorExit: - - AFB_ApiWarning(api, "CtlSubscribeEventCB: ignored unsupported event Numid=%i", snd_ctl_elem_info_get_numid(elemInfo)); - for (int idx = 0; idx < count; idx++) { - long valueL; - - switch (elemType) { - case SND_CTL_ELEM_TYPE_BOOLEAN: - valueL = snd_ctl_elem_value_get_boolean(elemData, idx); - AFB_ApiNotice(api, "CtlElemIdGetBool: value=%ld", valueL); - break; - case SND_CTL_ELEM_TYPE_INTEGER: - valueL = snd_ctl_elem_value_get_integer(elemData, idx); - AFB_ApiNotice(api, "CtlElemIdGetInt: value=%ld", valueL); - break; - case SND_CTL_ELEM_TYPE_INTEGER64: - valueL = snd_ctl_elem_value_get_integer64(elemData, idx); - AFB_ApiNotice(api, "CtlElemIdGetInt64: value=%ld", valueL); - break; - case SND_CTL_ELEM_TYPE_ENUMERATED: - valueL = snd_ctl_elem_value_get_enumerated(elemData, idx); - AFB_ApiNotice(api, "CtlElemIdGetEnum: value=%ld", valueL); - break; - case SND_CTL_ELEM_TYPE_BYTES: - valueL = snd_ctl_elem_value_get_byte(elemData, idx); - AFB_ApiNotice(api, "CtlElemIdGetByte: value=%ld", valueL); - break; - case SND_CTL_ELEM_TYPE_IEC958: - default: - AFB_ApiNotice(api, "CtlElemIdGetInt: Unsupported type=%d", elemType); - break; - } - } - return -1; -} - -STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData) { - int error; - SubscribeHandleT *subscribeHandle = (SubscribeHandleT*) userData; - snd_ctl_event_t *eventId; - snd_ctl_elem_id_t *elemId; - snd_ctl_elem_info_t *elemInfo; - long value; - - if ((revents & EPOLLHUP) != 0) { - AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB hanghup [card:%s disconnected]", subscribeHandle->info); - goto OnSuccessExit; - } - - if ((revents & EPOLLIN) == 0) goto OnSuccessExit; - - // initialise event structure on stack - snd_ctl_event_alloca(&eventId); - snd_ctl_elem_id_alloca(&elemId); - - error = snd_ctl_read(subscribeHandle->ctlDev, eventId); - if (error < 0) goto OnErrorExit; - - // we only process sndctrl element - if (snd_ctl_event_get_type(eventId) != SND_CTL_EVENT_ELEM) goto OnSuccessExit; - - // we only process value changed events - unsigned int eventMask = snd_ctl_event_elem_get_mask(eventId); - if (!(eventMask & SND_CTL_EVENT_MASK_VALUE)) goto OnSuccessExit; - - // extract element from event and get value - snd_ctl_event_elem_get_id(eventId, elemId); - error= CtlElemIdGetInt (subscribeHandle->api, subscribeHandle->ctlDev, elemId, &value); - if (error) goto OnErrorExit; - - // Fulup we have to reload info as elemId from event does not hold numid - snd_ctl_elem_info_alloca(&elemInfo); - snd_ctl_elem_info_set_id(elemInfo, elemId); - if (snd_ctl_elem_info(subscribeHandle->ctlDev, elemInfo) < 0) goto OnErrorExit; - int numid= snd_ctl_elem_info_get_numid(elemInfo); - - - for (int idx=0; idx < AudioStreamHandle.count; idx++) { - if (AudioStreamHandle.numid[idx] == numid) { - const char *pcmName = snd_pcm_name(AudioStreamHandle.pcm[idx]); - AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d pcm=%s pause=%d numid=%d", subscribeHandle->info, subscribeHandle->tid, pcmName, !value, numid); - snd_pcm_pause(AudioStreamHandle.pcm[idx], !value); - break; - } - } - -OnSuccessExit: - return 0; - -OnErrorExit: - AFB_ApiWarning(subscribeHandle->api, "CtlSubscribeEventCB: ignored unsupported event"); - return 0; -} - -static void *LoopInThread(void *handle) { - SubscribeHandleT *subscribeHandle = (SubscribeHandleT*) handle; - int count = 0; - int watchdog = MAINLOOP_WATCHDOG * 1000; - subscribeHandle->tid = (int) syscall(SYS_gettid); - - AFB_ApiNotice(subscribeHandle->api, "LoopInThread:%s/%d Started", subscribeHandle->info, subscribeHandle->tid); - - /* loop until end */ - for (;;) { - int res = sd_event_run(subscribeHandle->sdLoop, watchdog); - if (res == 0) { - AFB_ApiNotice(subscribeHandle->api, "LoopInThread:%s/%d Idle count=%d", subscribeHandle->info, subscribeHandle->tid, count++); - continue; - } - if (res < 0) { - AFB_ApiError(subscribeHandle->api, "LoopInThread:%s/%d ERROR=%i Exit errno=%s", subscribeHandle->info, subscribeHandle->tid, res, strerror(res)); - break; - } - } - pthread_exit(0); -} - -PUBLIC snd_ctl_t* AlsaCrlFromPcm(CtlSourceT *source, snd_pcm_t *pcm) { - char buffer[32]; - int error; - snd_ctl_t *ctlDev; - snd_pcm_info_t *pcmInfo; - - snd_pcm_info_alloca(&pcmInfo); - if ((error = snd_pcm_info(pcm, pcmInfo)) < 0) goto OnErrorExit; - - int pcmCard = snd_pcm_info_get_card(pcmInfo); - snprintf(buffer, sizeof(buffer), "hw:%i", pcmCard); - if ((error = snd_ctl_open(&ctlDev, buffer, SND_CTL_READONLY)) < 0) goto OnErrorExit; - - return ctlDev; - -OnErrorExit: - return NULL; -} - -PUBLIC snd_ctl_elem_id_t * AlsaCtlGetElemId(CtlSourceT *source, snd_ctl_t* ctlDev, int numid) { - char string[32]; - int error; - int index; - snd_ctl_elem_list_t *ctlList = NULL; - snd_ctl_elem_id_t *elemId; - - snd_ctl_elem_list_alloca(&ctlList); - - if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { - AFB_ApiError(source->api, "AlsaCtlElemIdGetInt [%s] fail retrieve controls", AlsaCtlUID (ctlDev, string)); - goto OnErrorExit; - } - - if ((error = snd_ctl_elem_list_alloc_space(ctlList, snd_ctl_elem_list_get_count(ctlList))) < 0) { - AFB_ApiError(source->api, "AlsaCtlElemIdGetInt [%s] fail retrieve count", AlsaCtlUID (ctlDev, string)); - goto OnErrorExit; - } - - // Fulup: do not understand why snd_ctl_elem_list should be call twice to get a valid ctlCount - if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { - AFB_ApiError(source->api, "AlsaCtlElemIdGetInt [%s] fail retrieve controls", AlsaCtlUID (ctlDev, string)); - goto OnErrorExit; - } - - // loop on control to find the right one - int ctlCount = snd_ctl_elem_list_get_used(ctlList); - for (index = 0; index < ctlCount; index++) { - - if (numid == snd_ctl_elem_list_get_numid(ctlList, index)) { - snd_ctl_elem_id_malloc(&elemId); - snd_ctl_elem_list_get_id(ctlList, index, elemId); - break; - } - } - - if (index == ctlCount) { - AFB_ApiError(source->api, "AlsaCtlRegister [%s] fail get numid=%i count", AlsaCtlUID (ctlDev, string), numid); - goto OnErrorExit; - } - - // clear ctl list and return elemid - snd_ctl_elem_list_clear(ctlList); - return elemId; - -OnErrorExit: - if (ctlList) snd_ctl_elem_list_clear(ctlList); - return NULL; -} - - -PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t * ctlDev) { - int error; - char string [32]; - struct pollfd pfds; - SubscribeHandleT *subscribeHandle = malloc(sizeof (SubscribeHandleT)); - - subscribeHandle->api = source->api; - subscribeHandle->ctlDev = ctlDev; - subscribeHandle->info = "ctlEvt"; - - // subscribe for sndctl events attached to devid - if ((error = snd_ctl_subscribe_events(ctlDev, 1)) < 0) { - AFB_ApiError(source->api, "AlsaCtlSubscribe: fail sndcard=%s to subscribe events", AlsaCtlUID(ctlDev, string)); - goto OnErrorExit; - } - - // get pollfd attach to this sound board - int count = snd_ctl_poll_descriptors(ctlDev, &pfds, 1); - if (count != 1) { - AFB_ApiError(source->api, "AlsaCtlSubscribe: fail sndcard=%s get poll descriptors", AlsaCtlUID(ctlDev, string)); - goto OnErrorExit; - } - - // add poll descriptor to AGL systemd mainloop - if ((error = sd_event_new(&subscribeHandle->sdLoop)) < 0) { - fprintf(stderr, "AlsaCtlSubscribe: fail sndcard=%s creating a new loop", AlsaCtlUID(ctlDev, string)); - goto OnErrorExit; - } - - // register sound event to binder main loop - if ((error = sd_event_add_io(subscribeHandle->sdLoop, &subscribeHandle->evtsrc, pfds.fd, EPOLLIN, CtlSubscribeEventCB, subscribeHandle)) < 0) { - AFB_ApiError(source->api, "AlsaCtlSubscribe: Fail sndcard=%s adding mainloop", AlsaCtlUID(ctlDev, string)); - goto OnErrorExit; - } - - // start a thread with a mainloop to monitor Audio-Agent - if ((error = pthread_create(&subscribeHandle->thread, NULL, &LoopInThread, subscribeHandle)) < 0) { - AFB_ApiError(source->api, "AlsaCtlSubscribe: Fail sndcard=%s create waiting thread err=%d", AlsaCtlUID(ctlDev, string), error); - goto OnErrorExit; - } - - return 0; - -OnErrorExit: - return -1; -} - -PUBLIC int AlsaCtlRegister(CtlSourceT *source, snd_pcm_t *pcm, int numid) { - long value; - int error; - - // NumID are attached to sndcard retrieve ctldev from PCM - snd_ctl_t* ctlDev = AlsaCrlFromPcm(source, pcm); - if (!ctlDev) { - AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail attache sndcard", snd_pcm_name(pcm)); - goto OnErrorExit; - } - - // This is the first registration let's subscrive to event - if (AudioStreamHandle.count == 0) { - AlsaCtlSubscribe (source, ctlDev); - } - - snd_ctl_elem_id_t *elemId= AlsaCtlGetElemId(source, ctlDev, numid); - if (!elemId) { - AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail to find numid=%d", snd_pcm_name(pcm), numid); - goto OnErrorExit; - } - - if ((error = CtlElemIdGetInt (source->api, ctlDev, elemId, &value)) != 0) { - AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail to get numid=%d value", snd_pcm_name(pcm), numid); - goto OnErrorExit; - } - - AFB_ApiNotice(source->api, "AlsaCtlRegister [pcm=%s] numid=%d value=%ld", snd_pcm_name(pcm), numid, value); - - // store PCM in order to pause/resume depending on event - AudioStreamHandle.pcm[AudioStreamHandle.count] = pcm; - AudioStreamHandle.numid[AudioStreamHandle.count] = numid; - AudioStreamHandle.count++; - - // we only need to keep ctldev open for initial registration - if (AudioStreamHandle.count >1) snd_ctl_close(ctlDev); - - // toggle pause/resume (should be done after pcm_start) - if ((error = snd_pcm_pause(pcm, !value)) < 0) { - AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail to pause", snd_pcm_name(pcm)); - goto OnErrorExit; - } - - return 0; - -OnErrorExit: - - return -1; -} diff --git a/plugins/alsa/alsa-dmix.c b/plugins/alsa/alsa-dmix.c deleted file mode 100644 index d527657..0000000 --- a/plugins/alsa/alsa-dmix.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2018 "IoT.bzh" - * Author Fulup Ar Foll - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#define _GNU_SOURCE // needed for vasprintf - -#include "alsa-softmixer.h" - -static int uniqueIpcIndex = 1024; - - -PUBLIC snd_pcm_t* AlsaCreateDmix(CtlSourceT *source, const char *dmixName, const char *slaveName) { - - AFB_ApiNotice(source->api, "AlsaCreateDmix: start "); - - int cardIndex= snd_ctl_card_info_get_card(AlsaByPathInfo (source, "/dev/snd/by-id/usb-Focusrite_Scarlett_18i8_USB_10004EE6-00")); - - AFB_ApiNotice(source->api, "AlsaCreateDmix: card index=%d ", cardIndex); - - snd_pcm_t *dmixPcm; - snd_config_t *dmixConfig, *slaveConfig, *elemConfig; - snd_pcm_stream_t streamPcm = SND_PCM_STREAM_PLAYBACK; - int error = 0, streamMode = SND_PCM_NONBLOCK; - - - error += snd_config_top(&dmixConfig); - error += snd_config_imake_integer(&elemConfig, "ipc_key", uniqueIpcIndex++); - error += snd_config_add(dmixConfig, elemConfig); - if (error) goto OnErrorExit; - - error += snd_config_make_compound(&slaveConfig, "slave", 0); - error += snd_config_imake_string(&elemConfig, "pcm", slaveName); - error += snd_config_add(slaveConfig, elemConfig); - if (error) goto OnErrorExit; - - // add leaf into config - error += snd_config_add(dmixConfig, slaveConfig); - if (error) goto OnErrorExit; - - snd_config_update(); - //AlsaDumpConfig (source, snd_config, 1); - AlsaDumpCtlConfig (source, dmixConfig, 1); - - - error = _snd_pcm_dmix_open(&dmixPcm, dmixName, snd_config, dmixConfig, streamPcm , streamMode); - if (error) { - AFB_ApiError(source->api, "AlsaCreateDmix: fail to create DMIX=%s SLAVE=%s", dmixName, slaveName); - goto OnErrorExit; - } - - AlsaDumpPcmInfo(source, dmixPcm, "DmixPCM"); - - AFB_ApiNotice(source->api, "AlsaCreateDmix: done"); - - return dmixPcm; - -OnErrorExit: - AFB_ApiNotice(source->api, "AlsaCreateDmix: OnErrorExit"); - return NULL; -} \ No newline at end of file diff --git a/plugins/alsa/alsa-pcm.c b/plugins/alsa/alsa-pcm.c deleted file mode 100644 index f342888..0000000 --- a/plugins/alsa/alsa-pcm.c +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright(C) 2018 "IoT.bzh" - * Author Fulup Ar Foll - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http : //www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License - -for the specific language governing permissions and - * limitations under the License. - * - * reference : - * https://github.com/zonque/simple-alsa-loop/blob/master/loop.c - * https://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html#a31 - * - */ - -#define _GNU_SOURCE // needed for vasprintf - -#include "alsa-softmixer.h" -#include -#include - - - -#define BUFFER_FRAME_COUNT 1024 - - -typedef struct { - snd_pcm_t *pcmIn; - snd_pcm_t *pcmOut; - AFB_ApiT api; - sd_event_source* evtsrc; - void* buffer; - size_t frameSize; - unsigned int frameCount; - unsigned int channels; - sd_event *sdLoop; - pthread_t thread; - int tid; - char* info; -} AlsaPcmCopyHandleT; - -static int defaultPcmRate = 48000; -static int defaultPcmChannels = 2; -static snd_pcm_format_t defaultPcmFormat = SND_PCM_FORMAT_S16_LE; -static snd_pcm_access_t defaultPcmAccess = SND_PCM_ACCESS_RW_INTERLEAVED; - -STATIC int AlsaPeriodSize(snd_pcm_format_t pcmFormat) { - int pcmSampleSize; - - switch (pcmFormat) { - - case SND_PCM_FORMAT_S8: - case SND_PCM_FORMAT_U8: - pcmSampleSize = 1; - break; - - case SND_PCM_FORMAT_U16_LE: - case SND_PCM_FORMAT_U16_BE: - case SND_PCM_FORMAT_S16_LE: - case SND_PCM_FORMAT_S16_BE: - pcmSampleSize = 2; - break; - - case SND_PCM_FORMAT_U32_LE: - case SND_PCM_FORMAT_U32_BE: - case SND_PCM_FORMAT_S32_LE: - case SND_PCM_FORMAT_S32_BE: - pcmSampleSize = 4; - break; - - default: - pcmSampleSize = 0; - } - - return pcmSampleSize; -} - - -PUBLIC int AlsaPcmConf(CtlSourceT *source, snd_pcm_t *pcmHandle, snd_pcm_format_t pcmFormat, unsigned int pcmRate, unsigned int pcmChannels, AlsaPcmHwInfoT *pcmHwInfo) { - char string[32]; - int error; - snd_pcm_hw_params_t *pxmHwParams; - snd_pcm_sw_params_t *pxmSwParams; - - // retrieve hadware config from PCM - snd_pcm_hw_params_alloca(&pxmHwParams); - snd_pcm_hw_params_any(pcmHandle, pxmHwParams); - - error = snd_pcm_hw_params_set_access(pcmHandle, pxmHwParams, defaultPcmAccess); - if (error) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Interleave=%d mode error=%s", AlsaPcmUID(pcmHandle, string), defaultPcmAccess, snd_strerror(error)); - goto OnErrorExit; - }; - - if (pcmFormat == ALSA_PCM_DEFAULT_FORMAT) pcmFormat = defaultPcmFormat; - if (pcmFormat != SND_PCM_FORMAT_UNKNOWN) { - if ((error = snd_pcm_hw_params_set_format(pcmHandle, pxmHwParams, pcmFormat)) < 0) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Format=%d error=%s", AlsaPcmUID(pcmHandle, string), pcmFormat, snd_strerror(error)); - AlsaDumpFormats(source, pcmHandle); - goto OnErrorExit; - } - } - - if (pcmRate == ALSA_PCM_DEFAULT_RATE) pcmRate = defaultPcmRate; - pcmHwInfo->rate = pcmRate; - if ((error = snd_pcm_hw_params_set_rate_near(pcmHandle, pxmHwParams, &pcmHwInfo->rate, 0)) < 0) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Rate=%d error=%s", AlsaPcmUID(pcmHandle, string), pcmRate, snd_strerror(error)); - goto OnErrorExit; - } - - // check we got requested rate - if (pcmHwInfo->rate != pcmRate) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Rate ask=%dHz get=%dHz", AlsaPcmUID(pcmHandle, string), pcmRate, pcmHwInfo->rate); - goto OnErrorExit; - } - - if (pcmChannels == ALSA_PCM_DEFAULT_CHANNELS) pcmChannels = defaultPcmChannels; - if ((error = snd_pcm_hw_params_set_channels(pcmHandle, pxmHwParams, pcmChannels)) < 0) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Channels=%d current=%d mode error=%s", AlsaPcmUID(pcmHandle, string), pcmChannels, pcmHwInfo->channels, snd_strerror(error)); - goto OnErrorExit; - }; - - // store selected values - if ((error = snd_pcm_hw_params(pcmHandle, pxmHwParams)) < 0) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s apply hwparams error=%s", AlsaPcmUID(pcmHandle, string), snd_strerror(error)); - goto OnErrorExit; - } - - // check we effective hw params after optional format change - snd_pcm_hw_params_get_channels(pxmHwParams, &pcmHwInfo->channels); - snd_pcm_hw_params_get_format(pxmHwParams, &pcmHwInfo->format); - snd_pcm_hw_params_get_rate(pxmHwParams, &pcmHwInfo->rate, 0); - pcmHwInfo->sampleSize = AlsaPeriodSize(pcmHwInfo->format); - if (pcmHwInfo->sampleSize == 0) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s unsupported format format=%d", AlsaPcmUID(pcmHandle, string), pcmFormat); - goto OnErrorExit; - } - - // retrieve software config from PCM - snd_pcm_sw_params_alloca(&pxmSwParams); - snd_pcm_sw_params_current(pcmHandle, pxmSwParams); - - if ((error = snd_pcm_sw_params_set_avail_min(pcmHandle, pxmSwParams, 16)) < 0) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail to PCM=%s set_buffersize error=%s", AlsaPcmUID(pcmHandle, string), snd_strerror(error)); - goto OnErrorExit; - }; - - // push software params into PCM - if ((error = snd_pcm_sw_params(pcmHandle, pxmSwParams)) < 0) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail to push software=%s params error=%s", AlsaPcmUID(pcmHandle, string), snd_strerror(error)); - goto OnErrorExit; - }; - - - AFB_ApiNotice(source->api, "AlsaPcmConf: PCM=%s done", AlsaPcmUID(pcmHandle, string)); - return 0; - -OnErrorExit: - return -1; -} - -STATIC int AlsaPcmReadCB(sd_event_source* src, int fd, uint32_t revents, void* userData) { - char string[32]; - int error; - snd_pcm_sframes_t framesIn, framesOut, availIn, availOut; - AlsaPcmCopyHandleT *pcmCopyHandle = (AlsaPcmCopyHandleT*) userData; - - - // PCM has was closed - if ((revents & EPOLLHUP) != 0) { - AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PCM=%s hanghup/disconnected", AlsaPcmUID(pcmCopyHandle->pcmIn, string)); - goto ExitOnSuccess; - } - - // ignore any non input events - if ((revents & EPOLLIN) == 0) { - goto ExitOnSuccess; - } - - // Fulup this should be optimised to limit CPU usage when idle - snd_pcm_state_t pcmState = snd_pcm_state(pcmCopyHandle->pcmIn); - if (pcmState == SND_PCM_STATE_PAUSED) { - sleep(1); - } - - // When XRNS append try to restore PCM - if (pcmState == SND_PCM_STATE_XRUN) { - AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PCM=%s XRUN", AlsaPcmUID(pcmCopyHandle->pcmIn, string)); - snd_pcm_prepare(pcmCopyHandle->pcmIn); - } - - // when PCM suspending loop until ready to go - if (pcmState == SND_PCM_STATE_SUSPENDED) { - while (1) { - if ((error = snd_pcm_resume(pcmCopyHandle->pcmIn)) < 0) { - AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PCM=%s SUSPENDED fail to resume", AlsaPcmUID(pcmCopyHandle->pcmIn, string)); - sleep(1); // Fulup should be replace with corresponding AFB_timer - } else { - AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PCM=%s SUSPENDED success to resume", AlsaPcmUID(pcmCopyHandle->pcmIn, string)); - } - } - } - - // do we have waiting frame - availIn = snd_pcm_avail_update(pcmCopyHandle->pcmIn); - if (availIn <= 0) { - goto ExitOnSuccess; - } - - // do we have space to push frame - availOut = snd_pcm_avail_update(pcmCopyHandle->pcmOut); - if (availOut <= 0) { - snd_pcm_prepare(pcmCopyHandle->pcmOut); - goto ExitOnSuccess; - } - - // make sure we can push all input frame into output pcm without locking - if (availOut < availIn) availIn = availOut; - - // we get too many data ignore some - if (availIn > pcmCopyHandle->frameCount) { - AFB_ApiInfo(pcmCopyHandle->api, "AlsaPcmReadCB PcmIn=%s XRUN lost=%ld", AlsaPcmUID(pcmCopyHandle->pcmIn, string), availIn - pcmCopyHandle->frameCount); - availIn = pcmCopyHandle->frameCount; - } - - // effectively read pcmIn and push frame to pcmOut - framesIn = snd_pcm_readi(pcmCopyHandle->pcmIn, pcmCopyHandle->buffer, availIn); - if (framesIn < 0 || framesIn != availIn) { - AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PcmIn=%s UNDERUN frame=%ld", AlsaPcmUID(pcmCopyHandle->pcmIn, string), framesIn); - goto ExitOnSuccess; - } - - // In/Out frames transfer through buffer copy - framesOut = snd_pcm_writei(pcmCopyHandle->pcmOut, pcmCopyHandle->buffer, framesIn); - if (framesOut < 0 || framesOut != framesIn) { - AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PcmOut=%s UNDERUN/SUSPEND frameOut=%ld", AlsaPcmUID(pcmCopyHandle->pcmOut, string), framesOut); - goto ExitOnSuccess; - } - - if (framesIn != framesOut) { - AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PCM=%s Loosing frames=%ld", AlsaPcmUID(pcmCopyHandle->pcmOut, string), (framesIn - framesOut)); - goto ExitOnSuccess; - } - - // fprintf(stderr, "."); - - return 0; - - // Cannot handle error in callback -ExitOnSuccess: - return 0; -} - -static void *LoopInThread(void *handle) { - AlsaPcmCopyHandleT *pcmCopyHandle = (AlsaPcmCopyHandleT*) handle; - int count = 0; - int watchdog = MAINLOOP_WATCHDOG * 1000; - pcmCopyHandle->tid = (int) syscall(SYS_gettid); - - AFB_ApiNotice(pcmCopyHandle->api, "LoopInThread:%s/%d Started", pcmCopyHandle->info, pcmCopyHandle->tid); - - - /* loop until end */ - for (;;) { - int res = sd_event_run(pcmCopyHandle->sdLoop, watchdog); - if (res == 0) { - AFB_ApiNotice(pcmCopyHandle->api, "LoopInThread:%s/%d Idle count=%d", pcmCopyHandle->info, pcmCopyHandle->tid, count++); - continue; - } - if (res < 0) { - AFB_ApiError(pcmCopyHandle->api,"LoopInThread:%s/%d ERROR=%i Exit errno=%s.\n", pcmCopyHandle->info, pcmCopyHandle->tid, res, strerror(res)); - break; - } - } - pthread_exit(0); -} - -PUBLIC int AlsaPcmCopy(CtlSourceT *source, snd_pcm_t *pcmIn, snd_pcm_t *pcmOut, snd_pcm_format_t format, snd_pcm_access_t access, unsigned int rate, unsigned int channels) { - char string[32]; - struct pollfd *pcmInFds; - int error; - AlsaPcmHwInfoT infoIn, infoOut; - - - if (format == ALSA_PCM_DEFAULT_FORMAT) format = defaultPcmFormat; - if (rate == ALSA_PCM_DEFAULT_RATE) rate = defaultPcmRate; - if (access == ALSA_PCM_DEFAULT_ACCESS) access = defaultPcmAccess; - if (channels == ALSA_PCM_DEFAULT_CHANNELS) channels = defaultPcmChannels; - - // prepare PCM for capture and replay - error = AlsaPcmConf(source, pcmIn, format, rate, channels, &infoIn); - if (error) goto OnErrorExit; - - // Prepare PCM for usage - if ((error = snd_pcm_start(pcmIn)) < 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail to prepare PCM=%s error=%s", AlsaPcmUID(pcmIn, string), snd_strerror(error)); - goto OnErrorExit; - }; - - - error = AlsaPcmConf(source, pcmOut, infoIn.format, infoIn.rate, infoIn.channels, &infoOut); - if (error) goto OnErrorExit; - - // Prepare PCM for usage - if ((error = snd_pcm_prepare(pcmOut)) < 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail to start PCM=%s error=%s", AlsaPcmUID(pcmOut, string), snd_strerror(error)); - goto OnErrorExit; - }; - - if (infoIn.format != infoOut.format) { - AFB_ApiError(source->api, "AlsaPcmCopy: pcmIn=%s pcmOut=%s format mismatch in=%d out=%d" - , AlsaPcmUID(pcmIn, string), AlsaPcmUID(pcmOut, string), infoIn.format, infoOut.format); - goto OnErrorExit; - } - - if (infoIn.channels != infoOut.channels) { - AFB_ApiError(source->api, "AlsaPcmCopy: pcmIn=%s pcmOut=%s channel count mismatch in=%d out=%d" - , AlsaPcmUID(pcmIn, string), AlsaPcmUID(pcmOut, string), infoIn.channels, infoOut.channels); - goto OnErrorExit; - } - - AlsaPcmCopyHandleT *pcmCopyHandle = malloc(sizeof (AlsaPcmCopyHandleT)); - pcmCopyHandle->info = "pcmCpy"; - pcmCopyHandle->pcmIn = pcmIn; - pcmCopyHandle->pcmOut = pcmOut; - pcmCopyHandle->api = source->api; - pcmCopyHandle->channels = infoIn.channels; - pcmCopyHandle->frameSize = infoIn.channels * infoIn.sampleSize; - pcmCopyHandle->frameCount = BUFFER_FRAME_COUNT; - pcmCopyHandle->buffer = malloc(pcmCopyHandle->frameCount * pcmCopyHandle->frameSize); - - // get FD poll descriptor for capture PCM - int pcmInCount = snd_pcm_poll_descriptors_count(pcmCopyHandle->pcmIn); - if (pcmInCount <= 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail pcmIn=%s get fds count error=%s", AlsaPcmUID(pcmIn, string), snd_strerror(error)); - goto OnErrorExit; - }; - - pcmInFds = alloca(sizeof (*pcmInFds) * pcmInCount); - if ((error = snd_pcm_poll_descriptors(pcmIn, pcmInFds, pcmInCount)) < 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail pcmIn=%s get pollfds error=%s", AlsaPcmUID(pcmOut, string), snd_strerror(error)); - goto OnErrorExit; - }; - - // add poll descriptor to AGL systemd mainloop - if ((error = sd_event_new(&pcmCopyHandle->sdLoop)) < 0) { - fprintf(stderr, "LaunchCallRequest: fail pcmin=%s creating a new loop: %s\n", AlsaPcmUID(pcmOut, string), strerror(error)); - goto OnErrorExit; - } - - for (int idx = 0; idx < pcmInCount; idx++) { - if ((error = sd_event_add_io(pcmCopyHandle->sdLoop, &pcmCopyHandle->evtsrc, pcmInFds[idx].fd, EPOLLIN, AlsaPcmReadCB, pcmCopyHandle)) < 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail pcmIn=%s sd_event_add_io err=%d", AlsaPcmUID(pcmIn, string), error); - goto OnErrorExit; - } - } - - // start a thread with a mainloop to monitor Audio-Agent - if ((error = pthread_create(&pcmCopyHandle->thread, NULL, &LoopInThread, pcmCopyHandle)) < 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail create waiting thread pcmIn=%s err=%d", AlsaPcmUID(pcmIn, string), error); - goto OnErrorExit; - } - - return 0; - -OnErrorExit: - AFB_ApiError(source->api, "AlsaPcmCopy: Fail \n - pcmIn=%s \n - pcmOut=%s", AlsaPcmUID(pcmIn, string), AlsaPcmUID(pcmOut, string)); - - return -1; -} - diff --git a/plugins/alsa/alsa-plug-dmix.c b/plugins/alsa/alsa-plug-dmix.c new file mode 100644 index 0000000..3d67410 --- /dev/null +++ b/plugins/alsa/alsa-plug-dmix.c @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author Fulup Ar Foll + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#define _GNU_SOURCE // needed for vasprintf + +#include "alsa-softmixer.h" + +static int uniqueIpcIndex = 1024; + +ALSA_PLUG_PROTO(dmix); + + +PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave) { + + snd_config_t *dmixConfig, *slaveConfig, *elemConfig, *pcmConfig; + AlsaPcmInfoT *pcmPlug= malloc(sizeof(AlsaPcmInfoT)); + pcmPlug->devid= pcmName; + int error=0; + + // refresh global alsalib config and create PCM top config + snd_config_update(); + error += snd_config_top(&dmixConfig); + error += snd_config_set_id (dmixConfig, pcmPlug->devid); + error += snd_config_imake_string(&elemConfig, "type", "dmix"); + error += snd_config_add(dmixConfig, elemConfig); + error += snd_config_imake_integer(&elemConfig, "ipc_key", uniqueIpcIndex++); + error += snd_config_add(dmixConfig, elemConfig); + if (error) goto OnErrorExit; + + error += snd_config_make_compound(&slaveConfig, "slave", 0); + error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->devid); + error += snd_config_add(slaveConfig, elemConfig); + if (error) goto OnErrorExit; + + // add leaf into config + error += snd_config_add(dmixConfig, slaveConfig); + if (error) goto OnErrorExit; + + error = _snd_pcm_dmix_open(&pcmPlug->handle, pcmPlug->devid, snd_config, dmixConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); + if (error) { + AFB_ApiError(source->api, "AlsaCreateDmix: fail to create DMIX=%s SLAVE=%s", pcmPlug->devid, pcmSlave->devid); + goto OnErrorExit; + } + + error += snd_config_search(snd_config, "pcm", &pcmConfig); + error += snd_config_add(pcmConfig, dmixConfig); + if (error) { + AFB_ApiError(source->api, "AlsaCreateDmix: fail to add configDMIX=%s", pcmPlug->devid); + goto OnErrorExit; + } + + // Debug config & pcm + AlsaDumpCtlConfig (source, dmixConfig, 1); + //AlsaDumpPcmInfo(source, pcmPlug->handle, pcmPlug->devid); + AFB_ApiNotice(source->api, "AlsaCreateDmix: %s done", pcmPlug->devid); + return pcmPlug; + +OnErrorExit: + AFB_ApiNotice(source->api, "AlsaCreateDmix: OnErrorExit"); + return NULL; +} \ No newline at end of file diff --git a/plugins/alsa/alsa-plug-vol.c b/plugins/alsa/alsa-plug-vol.c new file mode 100644 index 0000000..6de4ddf --- /dev/null +++ b/plugins/alsa/alsa-plug-vol.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author Fulup Ar Foll + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#define _GNU_SOURCE // needed for vasprintf + +#include "alsa-softmixer.h" + +ALSA_PLUG_PROTO(softvol); + +PUBLIC AlsaPcmInfoT* AlsaCreateVol(CtlSourceT *source, const char *pcmName, AlsaPcmInfoT* ctlTarget, AlsaPcmInfoT* pcmSlave) { + + snd_config_t *volConfig, *elemConfig, *slaveConfig, *controlConfig,*pcmConfig; + int error = 0; + AlsaPcmInfoT *pcmPlug= malloc(sizeof(AlsaPcmInfoT)); + pcmPlug->devid= pcmName; + + // refresh global alsalib config and create PCM top config + snd_config_update(); + error += snd_config_top(&volConfig); + + // add slave leaf + error += snd_config_make_compound(&slaveConfig, "slave", 0); + error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->devid); + error += snd_config_add(slaveConfig, elemConfig); + error += snd_config_add(volConfig, slaveConfig); + if (error) goto OnErrorExit; + + // add control leaf + error += snd_config_make_compound(&controlConfig, "control", 0); + error += snd_config_imake_string(&elemConfig, "name", pcmName); + error += snd_config_add(controlConfig, elemConfig); + error += snd_config_imake_integer(&elemConfig, "card", ctlTarget->cardid); + error += snd_config_add(controlConfig, elemConfig); + error += snd_config_add(volConfig, controlConfig); + if (error) goto OnErrorExit; + + // update top config to access previous plugin PCM + snd_config_update(); + + error = _snd_pcm_softvol_open(&pcmPlug->handle, pcmName, snd_config, volConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); + if (error) { + AFB_ApiError(source->api, "AlsaCreateVol: fail to create Plug=%s Slave=%s", pcmPlug->devid, pcmSlave->devid); + goto OnErrorExit; + } + + error += snd_config_search(snd_config, "pcm", &pcmConfig); + error += snd_config_add(pcmConfig, volConfig); + if (!error) { + AFB_ApiError(source->api, "AlsaCreateDmix: fail to add configDMIX=%s", pcmPlug->devid); + goto OnErrorExit; + } + + // Debug config & pcm + AlsaDumpCtlConfig (source, volConfig, 1); + //AlsaDumpPcmInfo(source, pcmPlug->handle, "pcmPlug->handle"); + AFB_ApiNotice(source->api, "AlsaCreateVol: %s done", pcmPlug->devid); + return pcmPlug; + +OnErrorExit: + AFB_ApiNotice(source->api, "AlsaCreateVol: OnErrorExit"); + return NULL; +} \ No newline at end of file diff --git a/plugins/alsa/alsa-softmixer.c b/plugins/alsa/alsa-softmixer.c index 253a00b..9586fa3 100644 --- a/plugins/alsa/alsa-softmixer.c +++ b/plugins/alsa/alsa-softmixer.c @@ -29,71 +29,86 @@ CTLP_ONLOAD(plugin, callbacks) { return NULL; } +CTLP_LUA2C(AlsaDmix, source, argsJ, responseJ) { + json_object* subscribeArgsJ = NULL; + + int error = 0; + wrap_json_pack(&subscribeArgsJ, "{ss}", "value", "location"); + AFB_ApiNotice(source->api, "lua2c router with %s", json_object_to_json_string_ext(subscribeArgsJ, JSON_C_TO_STRING_PRETTY)); + + return error; +} + + CTLP_LUA2C(AlsaRouter, source, argsJ, responseJ) { - json_object *devInJ, *devOutJ, *paramsJ=NULL; - AlsaDevByPathT devIn, devOut; - int err; + json_object *sndInJ, *sndOutJ, *paramsJ=NULL; + AlsaPcmInfoT *sndIn, *sndOut; + int error; - // init default values - memset(&devIn,0,sizeof(AlsaDevByPathT)); - memset(&devOut,0,sizeof(AlsaDevByPathT)); - int rate=ALSA_PCM_DEFAULT_RATE; - int channels=ALSA_PCM_DEFAULT_CHANNELS; - snd_pcm_format_t format=ALSA_PCM_DEFAULT_FORMAT; - snd_pcm_access_t access=ALSA_PCM_DEFAULT_ACCESS; + // make sndIn/Out a pointer to get cleaner code + sndIn=calloc(1, sizeof(AlsaPcmInfoT)); + sndOut=calloc(1, sizeof(AlsaPcmInfoT)); + // set pcm options to defaults + AlsaPcmHwInfoT *pcmOpts; + pcmOpts=calloc(1, sizeof(AlsaPcmHwInfoT)); + pcmOpts->format=SND_PCM_FORMAT_UNKNOWN; + pcmOpts->access=SND_PCM_ACCESS_RW_INTERLEAVED; - err= wrap_json_unpack(argsJ, "{s:o,s:o,s?o}", "devin", &devInJ, "devout", &devOutJ, "params", ¶msJ); - if (err) { + error= wrap_json_unpack(argsJ, "{s:o,s:o,s?o}", "devin", &sndInJ, "devout", &sndOutJ, "params", ¶msJ); + if (error) { AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter ARGS missing devIn|devOut args=%s", json_object_get_string(argsJ)); goto OnErrorExit; } - AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter **** PARAMS missing 'format|access|rate|channels' params=%s", json_object_get_string(paramsJ)); - - err= wrap_json_unpack(devInJ, "{s?s,s?s,s?i,s?i}", "path", &devIn.devpath, "id",&devIn.devid,"numid",&devIn.numid,"dev", &devIn.device, "sub", &devIn.subdev); - if (err || (!devIn.devpath && !devIn.devid)) { - AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter DEV-IN missing 'path|id|dev|sub|numid' devin=%s", json_object_get_string(devInJ)); + error= wrap_json_unpack(sndInJ, "{s?s,s?s,s?i,s?i,s?i}", "path",&sndIn->devpath, "id",&sndIn->devid, "numid",&sndIn->numid, "dev",&sndIn->device, "sub",&sndIn->subdev); + if (error || (!sndIn->devpath && !sndIn->devid)) { + AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter DEV-IN missing 'path|id|dev|sub|numid' devin=%s", json_object_get_string(sndInJ)); goto OnErrorExit; } - err= wrap_json_unpack(devOutJ, "{s?s,s?s,s?i, s?i,s?i}", "path", &devOut.devpath, "id", &devOut.device, "sub", &devOut.subdev); - if (err || (!devOut.devpath && !devOut.devid)) { - AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter DEV-OUT missing 'path|id|dev|sub' devout=%s", json_object_get_string(devOutJ)); + error= wrap_json_unpack(sndOutJ, "{s?s,s?s,s?i,s?i, s?i}", "path",&sndOut->devpath, "id",&sndOut->devid, "numid",&sndOut->numid,"dev",&sndOut->device, "sub",&sndOut->subdev); + if (error || (!sndOut->devpath && !sndOut->devid)) { + AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter DEV-OUT missing 'path|id|dev|sub' devout=%s", json_object_get_string(sndOutJ)); goto OnErrorExit; } - if (paramsJ) if ((err= wrap_json_unpack(paramsJ, "{s?i, s?i, s?i, s?i}", "format", &format, "access", &access, "rate", &rate, "channels",&channels)) != 0) { + if (paramsJ) if ((error= wrap_json_unpack(paramsJ, "{s?i, s?i, s?i, s?i}", "format", &pcmOpts->format, "access", &pcmOpts->access, "rate", &pcmOpts->rate, "channels",&pcmOpts->channels)) != 0) { AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter PARAMS missing 'format|access|rate|channels' params=%s", json_object_get_string(paramsJ)); goto OnErrorExit; } - AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter devIn=%s devOut=%s rate=%d channel=%d", devIn.devpath, devOut.devpath, rate, channels); - - // open PCM and start frame copy from binder Mainloop - snd_pcm_t* pcmIn = AlsaByPathOpenPcm(source, &devIn, SND_PCM_STREAM_CAPTURE); - snd_pcm_t* pcmOut = AlsaByPathOpenPcm(source, &devOut, SND_PCM_STREAM_PLAYBACK); - err = AlsaPcmCopy(source, pcmIn, pcmOut, format, access, (unsigned int)rate, (unsigned int)channels); - if(err) goto OnErrorExit; + AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter devin=%s devout=%s rate=%d channel=%d", sndIn->devpath, sndOut->devpath, pcmOpts->rate, pcmOpts->channels); + + // Check sndOut Exist and build a valid devid config + error= AlsaByPathDevid (source, sndOut); + if (error) goto OnErrorExit; + + //AlsaPcmInfoT *pcmOut = AlsaByPathOpenPcm(source, sndOut, SND_PCM_STREAM_PLAYBACK); + + // open capture PCM + AlsaPcmInfoT *pcmIn = AlsaByPathOpenPcm(source, sndIn, SND_PCM_STREAM_CAPTURE); + if (!pcmIn) goto OnErrorExit; + + AlsaPcmInfoT *pcmDmix= AlsaCreateDmix(source, "DmixPlugPcm", sndOut); + if(!pcmDmix) goto OnErrorExit; + + AlsaPcmInfoT *pcmVol= AlsaCreateVol(source, "SoftVol", sndIn, pcmDmix); + if(!pcmVol) goto OnErrorExit; + + error = AlsaPcmCopy(source, pcmIn, pcmVol, pcmOpts); + if(error) goto OnErrorExit; // Registration to event should be done after pcm_start - if (devIn.numid) { - err= AlsaCtlRegister(source, pcmIn, devIn.numid); - if(err) goto OnErrorExit; + if (sndIn->numid) { + error= AlsaCtlRegister(source, pcmIn, sndIn->numid); + if(error) goto OnErrorExit; } return 0; OnErrorExit: + AFB_ApiNotice(source->api, "--lua2c-- ERROR AlsaRouter sndIn=%s sndOut=%s rate=%d channel=%d", sndIn->devpath, sndOut->devpath, pcmOpts->rate, pcmOpts->channels); return -1; } -CTLP_LUA2C(AlsaDmix, source, argsJ, responseJ) { - json_object* subscribeArgsJ = NULL; - - int err = 0; - wrap_json_pack(&subscribeArgsJ, "{ss}", "value", "location"); - AFB_ApiNotice(source->api, "lua2c router with %s", json_object_to_json_string_ext(subscribeArgsJ, JSON_C_TO_STRING_PRETTY)); - - return err; -} diff --git a/plugins/alsa/alsa-softmixer.h b/plugins/alsa/alsa-softmixer.h index c777f5d..17e7cf2 100644 --- a/plugins/alsa/alsa-softmixer.h +++ b/plugins/alsa/alsa-softmixer.h @@ -31,63 +31,61 @@ #include -#define MAINLOOP_WATCHDOG 10000 +#define MAINLOOP_WATCHDOG 30000 #define MAX_AUDIO_STREAMS 8 -// Provide proto for LibASound low level API -int _snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name, - snd_config_t *root, snd_config_t *conf, - snd_pcm_stream_t stream, int mode); +#define ALSA_PLUG_PROTO(plugin) \ + int _snd_pcm_ ## plugin ## _open(snd_pcm_t **pcmp, const char *name, snd_config_t *root, snd_config_t *conf, snd_pcm_stream_t stream, int mode) -// alsa-ctl.c -PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo (CtlSourceT *source, const char *devid); -PUBLIC snd_ctl_t *AlsaCtlOpenCtl (CtlSourceT *source, const char *devid); -PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t *ctlDev); -PUBLIC int AlsaCtlRegister(CtlSourceT *source, snd_pcm_t *pcm, int numid); - -// alsa-tools-dump.c -PUBLIC void AlsaDumpFormats(CtlSourceT *source, snd_pcm_t *pcmHandle); -PUBLIC char *AlsaDumpPcmUid(snd_pcm_t *pcmHandle, char *buffer, size_t len); -PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle); -PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, snd_pcm_t *pcm, const char* info); -PUBLIC void AlsaDumpPcmParams(CtlSourceT *source, snd_pcm_hw_params_t *pcmHwParams); -PUBLIC void AlsaDumpCtlConfig(CtlSourceT *source, snd_config_t *config, int indent); -#define AlsaPcmUID(pcmHandle, buffer) AlsaDumpPcmUid(pcmHandle, buffer, sizeof(buffer)) -PUBLIC char *AlsaDumpCtlUid(snd_ctl_t *ctlHandle, char *buffer, size_t len); -#define AlsaCtlUID(ctlHandle, buffer) AlsaDumpCtlUid(ctlHandle, buffer, sizeof(buffer)) - -// alsa-tools-bypath.c +// alsa-utils-bypath.c typedef struct { - char *devpath; - char *devid; + const char *devpath; + const char *devid; int device; int subdev; int numid; -} AlsaDevByPathT; + int cardid; + snd_pcm_t *handle; +} AlsaPcmInfoT; -PUBLIC snd_ctl_card_info_t* AlsaByPathInfo (CtlSourceT *source, const char *control); -PUBLIC snd_pcm_t* AlsaByPathOpenPcm(CtlSourceT *source, AlsaDevByPathT *dev, snd_pcm_stream_t direction); -PUBLIC snd_ctl_t *AlsaByPathOpenCtl (CtlSourceT *source, AlsaDevByPathT *dev); -#define ALSA_PCM_DEFAULT_FORMAT (unsigned int)-99 -#define ALSA_PCM_DEFAULT_ACCESS (snd_pcm_access_t)-99 -#define ALSA_PCM_DEFAULT_RATE (snd_pcm_format_t)-99 -#define ALSA_PCM_DEFAULT_CHANNELS (unsigned int)-99 - -// alsa-pcm.c typedef struct { - snd_pcm_format_t format; + unsigned int rate; unsigned int channels; + snd_pcm_format_t format; + snd_pcm_access_t access; size_t sampleSize; - unsigned int rate; } AlsaPcmHwInfoT; -PUBLIC void AlsaPcmSetDefault (snd_pcm_format_t format, snd_pcm_access_t access, unsigned int rate, unsigned int channel); -PUBLIC int AlsaPcmConf(CtlSourceT *source, snd_pcm_t *pcmHandle, snd_pcm_format_t pcmFormat, unsigned int pcmRate, unsigned int pcmChannels, AlsaPcmHwInfoT *pcmHwInfo); -PUBLIC int AlsaPcmCopy(CtlSourceT *source, snd_pcm_t *pcmIn, snd_pcm_t *pcmOut, snd_pcm_format_t format, snd_pcm_access_t access, unsigned int rate, unsigned int channel); +PUBLIC snd_ctl_card_info_t* AlsaByPathInfo (CtlSourceT *source, const char *control); +PUBLIC AlsaPcmInfoT* AlsaByPathOpenPcm(CtlSourceT *source, AlsaPcmInfoT *dev, snd_pcm_stream_t direction); +PUBLIC snd_ctl_t *AlsaByPathOpenCtl (CtlSourceT *source, AlsaPcmInfoT *dev); +PUBLIC int AlsaByPathDevid (CtlSourceT *source, AlsaPcmInfoT *dev); + +// alsa-utils-dump.c +PUBLIC void AlsaDumpFormats(CtlSourceT *source, snd_pcm_t *pcmHandle); +PUBLIC char *AlsaDumpPcmUid(snd_pcm_t *pcmHandle, char *buffer, size_t len); +PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle); +PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, snd_pcm_t *pcm, const char* info); +PUBLIC void AlsaDumpPcmParams(CtlSourceT *source, snd_pcm_hw_params_t *pcmHwParams); +PUBLIC void AlsaDumpCtlConfig(CtlSourceT *source, snd_config_t *config, int indent); +#define ALSA_PCM_UID(pcmHandle, buffer) AlsaDumpPcmUid(pcmHandle, buffer, sizeof(buffer)) +PUBLIC char *AlsaDumpCtlUid(snd_ctl_t *ctlHandle, char *buffer, size_t len); +#define ALSA_CTL_UID(ctlHandle, buffer) AlsaDumpCtlUid(ctlHandle, buffer, sizeof(buffer)) + +// alsa-core-ctl.c +PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo (CtlSourceT *source, const char *devid); +PUBLIC snd_ctl_t *AlsaCtlOpenCtl (CtlSourceT *source, const char *devid); +PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t *ctlDev); +PUBLIC int AlsaCtlRegister(CtlSourceT *source, AlsaPcmInfoT *pcm, int numid); +PUBLIC int AlsaCtlGetNumidValueI(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long* value); + + -// alse-dmix.c -PUBLIC snd_pcm_t* AlsaCreateDmix(CtlSourceT *source, const char *dmixName, const char *slaveName); -PUBLIC snd_pcm_t* AlsaCreateCapture(CtlSourceT *source, const char* sndDevPath, unsigned int deviceIdx, unsigned int subdevIdx, unsigned int channelCount); +PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *opts); +PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pcmOut, AlsaPcmHwInfoT *opts); +// _snd_pcm_PLUGIN_open_ see macro ALSA_PLUG_PROTO(plugin) +PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave); +PUBLIC AlsaPcmInfoT* AlsaCreateVol(CtlSourceT *source, const char *pcmName, AlsaPcmInfoT* ctlTarget, AlsaPcmInfoT* pcmSlave); #endif \ No newline at end of file diff --git a/plugins/alsa/alsa-utils-bypath.c b/plugins/alsa/alsa-utils-bypath.c index e4a8833..8fba1f0 100644 --- a/plugins/alsa/alsa-utils-bypath.c +++ b/plugins/alsa/alsa-utils-bypath.c @@ -34,78 +34,93 @@ // Clone of AlsaLib snd_card_load2 static function -PUBLIC snd_ctl_card_info_t *AlsaByPathInfo (CtlSourceT *source, const char *devpath) { + +PUBLIC snd_ctl_card_info_t *AlsaByPathInfo(CtlSourceT *source, const char *devpath) { int open_dev; snd_ctl_card_info_t *cardInfo = malloc(snd_ctl_card_info_sizeof()); if (!devpath) goto OnErrorExit; - + open_dev = open(devpath, O_RDONLY); if (open_dev < 0) goto OnErrorExit; - + int rc = ioctl(open_dev, SNDRV_CTL_IOCTL_CARD_INFO(snd_ctl_card_info_sizeof()), cardInfo); if (rc < 0) { close(open_dev); goto OnErrorExit; } - + close(open_dev); return cardInfo; - + OnErrorExit: - AFB_ApiError(source->api,"AlsaCardInfoByPath: fail to find sndcard by path= %s", devpath); + AFB_ApiError(source->api, "AlsaCardInfoByPath: fail to find sndcard by path= %s", devpath); return NULL; } -PUBLIC snd_pcm_t* AlsaByPathOpenPcm(CtlSourceT *source, AlsaDevByPathT *dev, snd_pcm_stream_t direction) { - char pcmName[32]; - int error; - snd_pcm_t *pcmHandle; +PUBLIC int AlsaByPathDevid(CtlSourceT *source, AlsaPcmInfoT *dev) { // get card info from /dev/snd/xxx if not use hw:x,x,x - snd_ctl_card_info_t *cardInfo=NULL; + snd_ctl_card_info_t *cardInfo = NULL; if (dev->devpath) cardInfo = AlsaByPathInfo(source, dev->devpath); - else if (dev->devid) cardInfo = AlsaCtlGetInfo (source, dev->devid); + else if (dev->devid) cardInfo = AlsaCtlGetInfo(source, dev->devid); if (!cardInfo) { - AFB_ApiError(source->api, "AlsaByPathOpenPcm: fail to find sndcard by path=%s id=%s", dev->devpath, dev->devid); + AFB_ApiWarning(source->api, "AlsaByPathOpenPcm: fail to find sndcard by path=%s id=%s", dev->devpath, dev->devid); goto OnErrorExit; } // extract useful info from cardInfo handle - int cardIdx = snd_ctl_card_info_get_card(cardInfo); - const char *cardName = snd_ctl_card_info_get_name(cardInfo); + dev->cardid = snd_ctl_card_info_get_card(cardInfo); + + // if not provided build a valid PCM devid + if (!dev->devid) { + #define DEVID_MAX_LEN 32 + dev->devid=malloc(DEVID_MAX_LEN); + if (dev->subdev) snprintf((char*)dev->devid, DEVID_MAX_LEN, "hw:%i,%i,%i", dev->cardid, dev->device, dev->subdev); + else if (dev->device) snprintf((char*)dev->devid, DEVID_MAX_LEN, "hw:%i,%i", dev->cardid, dev->device); + else snprintf((char*)dev->devid, DEVID_MAX_LEN, "hw:%i", dev->cardid); + } + return 0; - // build a valid name and open sndcard - snprintf(pcmName, sizeof (pcmName), "hw:%i,%i,%i", cardIdx, dev->device, dev->subdev); - error = snd_pcm_open(&pcmHandle, pcmName, direction, SND_PCM_NONBLOCK); +OnErrorExit: + return -1; +} + +PUBLIC AlsaPcmInfoT* AlsaByPathOpenPcm(CtlSourceT *source, AlsaPcmInfoT *dev, snd_pcm_stream_t direction) { + int error; + + error = AlsaByPathDevid(source, dev); + if (error) goto OnErrorExit; + + error = snd_pcm_open(&dev->handle, dev->devid, direction, SND_PCM_NONBLOCK); if (error) { - AFB_ApiError(source->api, "AlsaByPathOpenPcm: fail openpcm (hw:%d -> %s name=%i subdev=%d): %s" - , cardIdx, cardName, dev->device, dev->subdev, snd_strerror(error)); + AFB_ApiError(source->api, "AlsaByPathOpenPcm: fail openpcm (devid=%s idxdev=%i subdev=%d): %s" + , dev->devid, dev->device, dev->subdev, snd_strerror(error)); goto OnErrorExit; } - - return (pcmHandle); + + return (dev); OnErrorExit: return NULL; } -PUBLIC snd_ctl_t *AlsaByPathOpenCtl (CtlSourceT *source, AlsaDevByPathT *dev) { +PUBLIC snd_ctl_t *AlsaByPathOpenCtl(CtlSourceT *source, AlsaPcmInfoT *dev) { int err; char devid[32]; snd_ctl_t *handle; - + // get card info from /dev/snd/xxx if not use hw:x,x,x - snd_ctl_card_info_t *cardInfo=NULL; + snd_ctl_card_info_t *cardInfo = NULL; if (dev->devpath) cardInfo = AlsaByPathInfo(source, dev->devpath); - else if (dev->devid) cardInfo = AlsaCtlGetInfo (source, dev->devid); + else if (dev->devid) cardInfo = AlsaCtlGetInfo(source, dev->devid); if (!cardInfo) { AFB_ApiError(source->api, "AlsaByPathOpenCtl: fail to find sndcard by path=%s id=%s", dev->devpath, dev->devid); goto OnErrorExit; } - + // extract useful info from cardInfo handle int cardIndex = snd_ctl_card_info_get_card(cardInfo); const char *cardId = snd_ctl_card_info_get_id(cardInfo); @@ -114,14 +129,14 @@ PUBLIC snd_ctl_t *AlsaByPathOpenCtl (CtlSourceT *source, AlsaDevByPathT *dev) { // build a valid name and open sndcard snprintf(devid, sizeof (devid), "hw:%i", cardIndex); if ((err = snd_ctl_open(&handle, devid, 0)) < 0) { - AFB_ApiError(source->api,"control open (hw:%d -> %s): %s", cardIndex, cardName, snd_strerror(err)); + AFB_ApiError(source->api, "control open (hw:%d -> %s): %s", cardIndex, cardName, snd_strerror(err)); goto OnErrorExit; } AFB_ApiNotice(source->api, "AlsaCtlOpenByPath: sndcard hw:%d id=%s name=%s", cardIndex, cardId, cardName); - free (cardInfo); + free(cardInfo); return handle; - + OnErrorExit: return NULL; } diff --git a/plugins/alsa/alsa-utils-dump.c b/plugins/alsa/alsa-utils-dump.c index 7cfa899..b3bc864 100644 --- a/plugins/alsa/alsa-utils-dump.c +++ b/plugins/alsa/alsa-utils-dump.c @@ -67,7 +67,7 @@ PUBLIC void AlsaDumpFormats(CtlSourceT *source, snd_pcm_t *pcmHandle) { snd_pcm_hw_params_alloca(&pxmHwParams); snd_pcm_hw_params_any(pcmHandle, pxmHwParams); - AFB_ApiNotice(source->api, "Available formats: PCM=%s", AlsaPcmUID(pcmHandle, string)); + AFB_ApiNotice(source->api, "Available formats: PCM=%s", ALSA_PCM_UID(pcmHandle, string)); for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) { if (snd_pcm_hw_params_test_format(pcmHandle, pxmHwParams, format) == 0) { AFB_ApiNotice(source->api, "- %s", snd_pcm_format_name(format)); -- cgit 1.2.3-korg