aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/alsa/alsa-ctl.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/alsa/alsa-ctl.c')
-rw-r--r--plugins/alsa/alsa-ctl.c399
1 files changed, 399 insertions, 0 deletions
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;
+}