summaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/alsa/alsa-capture.c80
-rw-r--r--plugins/alsa/alsa-ctl.c399
-rw-r--r--plugins/alsa/alsa-dmix.c27
-rw-r--r--plugins/alsa/alsa-lua.c45
-rw-r--r--plugins/alsa/alsa-pcm.c380
-rw-r--r--plugins/alsa/alsa-softmixer.c88
-rw-r--r--plugins/alsa/alsa-softmixer.h58
-rw-r--r--plugins/alsa/alsa-tools-high.c66
-rw-r--r--plugins/alsa/alsa-tools-low.c55
-rw-r--r--plugins/alsa/alsa-utils-bypath.c127
-rw-r--r--plugins/alsa/alsa-utils-dump.c204
11 files changed, 1324 insertions, 205 deletions
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 <fulup@iot.bzh>
+ *
+ * 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 <fulup@iot.bzh>
+ *
+ * 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 <pthread.h>
+#include <sys/syscall.h>
+
+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 <fulup@iot.bzh>
- *
- * 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 <fulup@iot.bzh>
+ *
+ * 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 <pthread.h>
+#include <sys/syscall.h>
+
+
+
+#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", &paramsJ);
+ 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 <afb/afb-binding.h>
#include <systemd/sd-event.h>
#include <json-c/json_object.h>
-#include <stdbool.h>
-#include <string.h>
+#include <stdio.h>
#include "ctl-plugin.h"
#include "wrap-json.h"
#include <alsa/asoundlib.h>
+#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 <fulup@iot.bzh>
- *
- * 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 <fulup@iot.bzh>
- *
- * 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 <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <ctype.h>
-#include <fcntl.h>
-#include <sys/ioctl.h>
-
-#include "alsa-softmixer.h"
-
-// extracted IOCTLs from <alsa/asoundlib.h>
-#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 <fulup@iot.bzh>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+#include "alsa-softmixer.h"
+
+// extracted IOCTLs from <alsa/asoundlib.h>
+#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 <fulup@iot.bzh>
+ *
+ * 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;
+ }
+ }
+}