From e904b7da51297b0417df31ab79568c3f1243fb64 Mon Sep 17 00:00:00 2001 From: Fulup Ar Foll Date: Tue, 8 May 2018 22:12:35 +0200 Subject: Fist AlsaLoop model playing music (work in progress) --- plugins/alsa/alsa-capture.c | 80 ++++++++ plugins/alsa/alsa-ctl.c | 399 +++++++++++++++++++++++++++++++++++++++ plugins/alsa/alsa-dmix.c | 27 ++- plugins/alsa/alsa-lua.c | 45 ----- plugins/alsa/alsa-pcm.c | 380 +++++++++++++++++++++++++++++++++++++ plugins/alsa/alsa-softmixer.c | 88 ++++++--- plugins/alsa/alsa-softmixer.h | 58 +++++- plugins/alsa/alsa-tools-high.c | 66 ------- plugins/alsa/alsa-tools-low.c | 55 ------ plugins/alsa/alsa-utils-bypath.c | 127 +++++++++++++ plugins/alsa/alsa-utils-dump.c | 204 ++++++++++++++++++++ 11 files changed, 1324 insertions(+), 205 deletions(-) create mode 100644 plugins/alsa/alsa-capture.c create mode 100644 plugins/alsa/alsa-ctl.c delete mode 100644 plugins/alsa/alsa-lua.c create mode 100644 plugins/alsa/alsa-pcm.c delete mode 100644 plugins/alsa/alsa-tools-high.c delete mode 100644 plugins/alsa/alsa-tools-low.c create mode 100644 plugins/alsa/alsa-utils-bypath.c create mode 100644 plugins/alsa/alsa-utils-dump.c (limited to 'plugins') diff --git a/plugins/alsa/alsa-capture.c b/plugins/alsa/alsa-capture.c new file mode 100644 index 0000000..992f731 --- /dev/null +++ b/plugins/alsa/alsa-capture.c @@ -0,0 +1,80 @@ +/* + * 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-ctl.c b/plugins/alsa/alsa-ctl.c new file mode 100644 index 0000000..b469e94 --- /dev/null +++ b/plugins/alsa/alsa-ctl.c @@ -0,0 +1,399 @@ +/* + * 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 index b162d04..d527657 100644 --- a/plugins/alsa/alsa-dmix.c +++ b/plugins/alsa/alsa-dmix.c @@ -23,12 +23,13 @@ static int uniqueIpcIndex = 1024; -PUBLIC int AlsaCreateDmix(CtlSourceT *source) { +PUBLIC snd_pcm_t* AlsaCreateDmix(CtlSourceT *source, const char *dmixName, const char *slaveName) { AFB_ApiNotice(source->api, "AlsaCreateDmix: start "); - int cardid= AlsaCardInfoByPath ("/dev/snd/by-id/usb-Focusrite_Scarlett_18i8_USB_10004EE6-00"); - AFB_ApiNotice(source->api, "AlsaCreateDmix: cardid=%d ", cardid); + 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; @@ -42,7 +43,7 @@ PUBLIC int AlsaCreateDmix(CtlSourceT *source) { if (error) goto OnErrorExit; error += snd_config_make_compound(&slaveConfig, "slave", 0); - error += snd_config_imake_string(&elemConfig, "pcm", "hw:6"); + error += snd_config_imake_string(&elemConfig, "pcm", slaveName); error += snd_config_add(slaveConfig, elemConfig); if (error) goto OnErrorExit; @@ -51,17 +52,23 @@ PUBLIC int AlsaCreateDmix(CtlSourceT *source) { if (error) goto OnErrorExit; snd_config_update(); - AlsaDumpConfig (source, snd_config, 1); - AlsaDumpConfig (source, dmixConfig, 1); - + //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; + } - int status = _snd_pcm_dmix_open(&dmixPcm, "MyDMix", snd_config, dmixConfig, streamPcm , streamMode); + AlsaDumpPcmInfo(source, dmixPcm, "DmixPCM"); AFB_ApiNotice(source->api, "AlsaCreateDmix: done"); - return status; + return dmixPcm; OnErrorExit: AFB_ApiNotice(source->api, "AlsaCreateDmix: OnErrorExit"); - return 1; + return NULL; } \ No newline at end of file diff --git a/plugins/alsa/alsa-lua.c b/plugins/alsa/alsa-lua.c deleted file mode 100644 index 8f1329f..0000000 --- a/plugins/alsa/alsa-lua.c +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2017 "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" - - -// Force Lua2cWrapper inclusion within already existing plugin -Lua2cWrapperT Lua2cWrap; - -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-- AlsaDmix"); - - return err; -} - -CTLP_LUA2C (AlsaRouter, 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-pcm.c b/plugins/alsa/alsa-pcm.c new file mode 100644 index 0000000..f342888 --- /dev/null +++ b/plugins/alsa/alsa-pcm.c @@ -0,0 +1,380 @@ +/* + * 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-softmixer.c b/plugins/alsa/alsa-softmixer.c index bf195fb..253a00b 100644 --- a/plugins/alsa/alsa-softmixer.c +++ b/plugins/alsa/alsa-softmixer.c @@ -14,42 +14,86 @@ * See the License for the specific language governing permissions and * limitations under the License. * -*/ + */ #define _GNU_SOURCE // needed for vasprintf #include "alsa-softmixer.h" -CTLP_CAPI_REGISTER("alsa-mixer"); +// Force Lua2cWrapper inclusion within already existing plugin +CTLP_LUA_REGISTER("alsa-mixer") // Call at initialisation time CTLP_ONLOAD(plugin, callbacks) { - AFB_ApiDebug (plugin->api, "SoftMixer plugin: uid='%s' 'info='%s'", plugin->uid, plugin->info); - - // fake action call during init for debug - CtlSourceT source; - source.api = plugin->api; - AlsaCreateDmix (&source); - - return 0; + AFB_ApiDebug (plugin->api, "SoftMixer plugin: uid='%s' 'info='%s'", plugin->uid, plugin->info); + return NULL; } -CTLP_CAPI (zone_ctl, source, argsJ, eventJ) { - json_object* subscribeArgsJ = NULL; +CTLP_LUA2C(AlsaRouter, source, argsJ, responseJ) { + json_object *devInJ, *devOutJ, *paramsJ=NULL; + AlsaDevByPathT devIn, devOut; + int err; + + // 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; + + + err= wrap_json_unpack(argsJ, "{s:o,s:o,s?o}", "devin", &devInJ, "devout", &devOutJ, "params", ¶msJ); + if (err) { + AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter ARGS missing devIn|devOut args=%s", json_object_get_string(argsJ)); + goto OnErrorExit; + } - int err = 0; - wrap_json_pack(&subscribeArgsJ, "{ss}", "value", "location"); - AFB_ApiDebug(source->api, "Calling zone_ctl with %s", json_object_to_json_string_ext(subscribeArgsJ, JSON_C_TO_STRING_PRETTY)); + AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter **** PARAMS missing 'format|access|rate|channels' params=%s", json_object_get_string(paramsJ)); - return err; + 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)); + 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)); + 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) { + 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; + + // Registration to event should be done after pcm_start + if (devIn.numid) { + err= AlsaCtlRegister(source, pcmIn, devIn.numid); + if(err) goto OnErrorExit; + } + + return 0; + +OnErrorExit: + return -1; } -CTLP_CAPI (stream_ctl, source, argsJ, eventJ) { - json_object* subscribeArgsJ = NULL; +CTLP_LUA2C(AlsaDmix, source, argsJ, responseJ) { + json_object* subscribeArgsJ = NULL; - int err = 0; - wrap_json_pack(&subscribeArgsJ, "{ss}", "value", "location"); - AFB_ApiDebug(source->api, "Calling stream_ctl with %s", json_object_to_json_string_ext(subscribeArgsJ, JSON_C_TO_STRING_PRETTY)); + 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; + return err; } diff --git a/plugins/alsa/alsa-softmixer.h b/plugins/alsa/alsa-softmixer.h index d413929..c777f5d 100644 --- a/plugins/alsa/alsa-softmixer.h +++ b/plugins/alsa/alsa-softmixer.h @@ -24,26 +24,70 @@ #include #include #include -#include -#include +#include #include "ctl-plugin.h" #include "wrap-json.h" #include +#define MAINLOOP_WATCHDOG 10000 +#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); -// alsa-tools-high.c -PUBLIC void AlsaDumpConfig (CtlSourceT *source, snd_config_t *config, int indent); +// 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 + +typedef struct { + char *devpath; + char *devid; + int device; + int subdev; + int numid; +} AlsaDevByPathT; + +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-tools-low.c -PUBLIC int AlsaCardInfoByPath (const char *control); +// alsa-pcm.c +typedef struct { + snd_pcm_format_t format; + unsigned int channels; + 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 int AlsaCreateDmix (CtlSourceT *source); +// 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); #endif \ No newline at end of file diff --git a/plugins/alsa/alsa-tools-high.c b/plugins/alsa/alsa-tools-high.c deleted file mode 100644 index a86ba2e..0000000 --- a/plugins/alsa/alsa-tools-high.c +++ /dev/null @@ -1,66 +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 void AlsaDumpConfig(CtlSourceT *source, snd_config_t *config, int indent) { - snd_config_iterator_t it, next; - - // hugly hack to get minimalist indentation - char *pretty = alloca(indent + 1); - for (int idx = 0; idx < indent; idx++) pretty[idx] = '-'; - pretty[indent] = '\0'; - - snd_config_for_each(it, next, config) { - snd_config_t *node = snd_config_iterator_entry(it); - const char *key; - - // ignore comment en empty lines - if (snd_config_get_id(node, &key) < 0) continue; - - switch (snd_config_get_type(node)) { - long valueI; - const char *valueS; - - case SND_CONFIG_TYPE_INTEGER: - snd_config_get_integer(node, &valueI); - AFB_ApiNotice(source->api, "DumpAlsaConfig: %s %s: %d (int)", pretty, key, (int) valueI); - break; - - case SND_CONFIG_TYPE_STRING: - snd_config_get_string(node, &valueS); - AFB_ApiNotice(source->api, "DumpAlsaConfig: %s %s: %s (str)", pretty, key, valueS); - break; - - case SND_CONFIG_TYPE_COMPOUND: - AFB_ApiNotice(source->api, "DumpAlsaConfig: %s %s { ", pretty, key); - AlsaDumpConfig(source, node, indent + 2); - AFB_ApiNotice(source->api, "DumpAlsaConfig: %s } ", pretty); - break; - - default: - snd_config_get_string(node, &valueS); - AFB_ApiNotice(source->api, "DumpAlsaConfig: %s: key=%s unknown=%s", pretty, key, valueS); - break; - } - } -} diff --git a/plugins/alsa/alsa-tools-low.c b/plugins/alsa/alsa-tools-low.c deleted file mode 100644 index 57b5e5c..0000000 --- a/plugins/alsa/alsa-tools-low.c +++ /dev/null @@ -1,55 +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 -#include -#include -#include -#include -#include -#include - -#include "alsa-softmixer.h" - -// extracted IOCTLs from -#define _IOR_HACKED(type,nr,size) _IOC(_IOC_READ,(type),(nr),size) -#define SNDRV_CTL_IOCTL_CARD_INFO(size) _IOR_HACKED('U', 0x01, size) - -// Clone of AlsaLib snd_card_load2 static function -int AlsaCardInfoByPath (const char *devpath) { - int open_dev; - snd_ctl_card_info_t *cardinfo = alloca(snd_ctl_card_info_sizeof()); - - open_dev = open(devpath, O_RDONLY); - if (open_dev >= 0) { - int rc = ioctl(open_dev, SNDRV_CTL_IOCTL_CARD_INFO(snd_ctl_card_info_sizeof()), cardinfo); - if (rc < 0) { - int err = -errno; - close(open_dev); - return err; - } - close(open_dev); - int cardId = snd_ctl_card_info_get_card(cardinfo); - return cardId; - } else { - return -errno; - } -} - diff --git a/plugins/alsa/alsa-utils-bypath.c b/plugins/alsa/alsa-utils-bypath.c new file mode 100644 index 0000000..e4a8833 --- /dev/null +++ b/plugins/alsa/alsa-utils-bypath.c @@ -0,0 +1,127 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#include "alsa-softmixer.h" + +// extracted IOCTLs from +#define _IOR_HACKED(type,nr,size) _IOC(_IOC_READ,(type),(nr),size) +#define SNDRV_CTL_IOCTL_CARD_INFO(size) _IOR_HACKED('U', 0x01, size) + + +// Clone of AlsaLib snd_card_load2 static function +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); + 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; + + // get card info from /dev/snd/xxx if not use hw:x,x,x + snd_ctl_card_info_t *cardInfo=NULL; + if (dev->devpath) cardInfo = AlsaByPathInfo(source, dev->devpath); + 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); + 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, dev->device, dev->subdev); + error = snd_pcm_open(&pcmHandle, pcmName, 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)); + goto OnErrorExit; + } + + return (pcmHandle); + +OnErrorExit: + return NULL; +} + +PUBLIC snd_ctl_t *AlsaByPathOpenCtl (CtlSourceT *source, AlsaDevByPathT *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; + if (dev->devpath) cardInfo = AlsaByPathInfo(source, dev->devpath); + 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); + const char *cardName = snd_ctl_card_info_get_name(cardInfo); + + // 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)); + goto OnErrorExit; + } + + AFB_ApiNotice(source->api, "AlsaCtlOpenByPath: sndcard hw:%d id=%s name=%s", cardIndex, cardId, cardName); + free (cardInfo); + return handle; + +OnErrorExit: + return NULL; +} diff --git a/plugins/alsa/alsa-utils-dump.c b/plugins/alsa/alsa-utils-dump.c new file mode 100644 index 0000000..7cfa899 --- /dev/null +++ b/plugins/alsa/alsa-utils-dump.c @@ -0,0 +1,204 @@ +/* + * 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 char *AlsaDumpPcmUid(snd_pcm_t *pcmHandle, char *buffer, size_t len) { + snd_pcm_info_t *pcmInfo; + snd_pcm_info_alloca(&pcmInfo); + + // retrieve PCM name for error/debug + int error = snd_pcm_info(pcmHandle, pcmInfo); + if (error) goto OnErrorExit; + + int pcmCard = snd_pcm_info_get_card(pcmInfo); + const char *pcmName = snd_pcm_info_get_name(pcmInfo); + snprintf(buffer, len, "hw:%i [%s]", pcmCard, pcmName); + return buffer; + +OnErrorExit: + return NULL; +} + +PUBLIC char *AlsaDumpCtlUid(snd_ctl_t *ctlHandle, char *buffer, size_t len) { + snd_ctl_card_info_t *ctlInfo; + + snd_ctl_card_info_alloca(&ctlInfo); + + // retrieve PCM name for error/debug + int error = snd_ctl_card_info(ctlHandle, ctlInfo); + if (error) goto OnErrorExit; + + const char *ctlId = snd_ctl_card_info_get_id(ctlInfo); + const char *ctlName = snd_ctl_card_info_get_name(ctlInfo); + snprintf(buffer, len, "hw:%s [%s]", ctlId, ctlName); + return buffer; + +OnErrorExit: + return NULL; +} + + +PUBLIC void AlsaDumpFormats(CtlSourceT *source, snd_pcm_t *pcmHandle) { + char string[32]; + snd_pcm_format_t format; + snd_pcm_hw_params_t *pxmHwParams; + + // retrieve hadware config from PCM + snd_pcm_hw_params_alloca(&pxmHwParams); + snd_pcm_hw_params_any(pcmHandle, pxmHwParams); + + AFB_ApiNotice(source->api, "Available formats: PCM=%s", AlsaPcmUID(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)); + } + } +} + + +PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) { + snd_ctl_card_info_t *cardInfo; + int err; + int dev= -1; + snd_pcm_info_t *pcminfo; + snd_pcm_info_alloca(&pcminfo); + unsigned int subdevCount, subdevAvail; + + snd_ctl_card_info_alloca(&cardInfo); + snd_ctl_card_info(handle, cardInfo); + int cardIndex = snd_ctl_card_info_get_card(cardInfo); + const char *cardId = snd_ctl_card_info_get_id(cardInfo); + const char *cardName = snd_ctl_card_info_get_name(cardInfo); + + // loop on every sndcard devices + while (1) { + + if (snd_ctl_pcm_next_device(handle, &dev) < 0) { + AFB_ApiError(source->api, "AlsaDumpCard: fail to open subdev card id=%s name=%s", cardId, cardName); + goto OnErrorExit; + } + + // no more devices + if (dev < 0) break; + + // ignore empty device slot + if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { + if (err != -ENOENT) + AFB_ApiError(source->api, "control digital audio info (%s): %s", cardName, snd_strerror(err)); + continue; + } + + AFB_ApiNotice(source->api,"AlsaDumpCard card %d: %s [%s], device %d: %s [%s]", + cardIndex, cardId, cardName, dev, snd_pcm_info_get_id(pcminfo), snd_pcm_info_get_name(pcminfo)); + + // loop on subdevices + subdevCount = snd_pcm_info_get_subdevices_count(pcminfo); + subdevAvail = snd_pcm_info_get_subdevices_avail(pcminfo); + + for (unsigned int idx = 0; idx < subdevCount; idx++) { + snd_pcm_info_set_subdevice(pcminfo, idx); + if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { + AFB_ApiError(source->api, "AlsaDumpCard: control digital audio playback info %i: %s", cardIndex, snd_strerror(err)); + } else { + AFB_ApiNotice(source->api, "AlsaDumpCard: -- Subdevice #%d: %s", idx, snd_pcm_info_get_subdevice_name(pcminfo)); + } + } + AFB_ApiNotice(source->api, "AlsaDumpCard => subdevice count=%d avaliable=%d", subdevCount, subdevAvail); + } + return; + +OnErrorExit: + return; +} + + +PUBLIC void AlsaDumpPcmParams(CtlSourceT *source, snd_pcm_hw_params_t *pcmHwParams) { + snd_output_t *output; + char *buffer; + + snd_output_buffer_open(&output); + snd_pcm_hw_params_dump(pcmHwParams, output); + snd_output_buffer_string(output, &buffer); + AFB_ApiNotice(source->api, "AlsaPCMDump: %s", buffer); + snd_output_close(output); +} + + +PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, snd_pcm_t *pcm, const char* info) { + snd_output_t *out; + char *buffer; + + // create an output buffer an dump PCM config + snd_output_buffer_open(&out); + snd_output_printf(out, info); + snd_output_printf(out, ": "); + snd_pcm_dump(pcm, out); + + snd_output_buffer_string(out, &buffer); + AFB_ApiNotice(source->api, "AlsaPCMDump: %s", buffer); + snd_output_close(out); +} + +PUBLIC void AlsaDumpCtlConfig(CtlSourceT *source, snd_config_t *config, int indent) { + snd_config_iterator_t it, next; + + // hugly hack to get minimalist indentation + char *pretty = alloca(indent + 1); + + for (int idx = 0; idx < indent; idx++) pretty[idx] = '-'; + pretty[indent] = '\0'; + + snd_config_for_each(it, next, config) { + snd_config_t *node = snd_config_iterator_entry(it); + const char *key; + + // ignore comment en empty lines + if (snd_config_get_id(node, &key) < 0) continue; + + switch (snd_config_get_type(node)) { + long valueI; + const char *valueS; + + case SND_CONFIG_TYPE_INTEGER: + snd_config_get_integer(node, &valueI); + AFB_ApiNotice(source->api, "DumpAlsaConfig: %s %s: %d (int)", pretty, key, (int) valueI); + break; + + case SND_CONFIG_TYPE_STRING: + snd_config_get_string(node, &valueS); + AFB_ApiNotice(source->api, "DumpAlsaConfig: %s %s: %s (str)", pretty, key, valueS); + break; + + case SND_CONFIG_TYPE_COMPOUND: + AFB_ApiNotice(source->api, "DumpAlsaConfig: %s %s { ", pretty, key); + AlsaDumpCtlConfig(source, node, indent + 2); + AFB_ApiNotice(source->api, "DumpAlsaConfig: %s } ", pretty); + break; + + default: + snd_config_get_string(node, &valueS); + AFB_ApiNotice(source->api, "DumpAlsaConfig: %s: key=%s unknown=%s", pretty, key, valueS); + break; + } + } +} -- cgit 1.2.3-korg