diff options
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/alsa/alsa-api-sndcards.c | 166 | ||||
-rw-r--r-- | plugins/alsa/alsa-api-sndloops.c | 135 | ||||
-rw-r--r-- | plugins/alsa/alsa-api-sndstreams.c | 161 | ||||
-rw-r--r-- | plugins/alsa/alsa-api-sndzones.c | 145 | ||||
-rw-r--r-- | plugins/alsa/alsa-core-ctl.c | 40 | ||||
-rw-r--r-- | plugins/alsa/alsa-core-pcm.c | 20 | ||||
-rw-r--r-- | plugins/alsa/alsa-plug-dmix.c | 29 | ||||
-rw-r--r-- | plugins/alsa/alsa-plug-multi.c | 105 | ||||
-rw-r--r-- | plugins/alsa/alsa-plug-route.c | 140 | ||||
-rw-r--r-- | plugins/alsa/alsa-plug-stream.c | 121 | ||||
-rw-r--r-- | plugins/alsa/alsa-plug-vol.c | 77 | ||||
-rw-r--r-- | plugins/alsa/alsa-softmixer.c | 93 | ||||
-rw-r--r-- | plugins/alsa/alsa-softmixer.h | 96 | ||||
-rw-r--r-- | plugins/alsa/alsa-utils-bypath.c | 65 | ||||
-rw-r--r-- | plugins/alsa/alsa-utils-dump.c | 110 |
15 files changed, 1249 insertions, 254 deletions
diff --git a/plugins/alsa/alsa-api-sndcards.c b/plugins/alsa/alsa-api-sndcards.c new file mode 100644 index 0000000..d48abd8 --- /dev/null +++ b/plugins/alsa/alsa-api-sndcards.c @@ -0,0 +1,166 @@ +/* + * 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" + +// Fulup need to be cleanup with new controller version +extern Lua2cWrapperT Lua2cWrap; + +STATIC int ProcessOneChannel(CtlSourceT *source, const char* uid, json_object *channelJ, AlsaPcmChannels *channel) { + const char*channelUid; + + int error = wrap_json_unpack(channelJ, "{ss,si !}", "uid", &channelUid, "port", &channel->port); + if (error) goto OnErrorExit; + + channel->uid=strdup(channelUid); + return 0; + +OnErrorExit: + AFB_ApiError(source->api, "ProcessOneChannel: sndcard=%s channel: missing (uid||port) json=%s", uid, json_object_get_string(channelJ)); + return -1; +} + +STATIC int ProcessSndParams(CtlSourceT *source, const char* uid, json_object *paramsJ, AlsaPcmHwInfoT *params) { + + int error = wrap_json_unpack(paramsJ, "{s?i,s?i !}", "rate", ¶ms->rate, "channels", ¶ms->channels); + if (error) goto OnErrorExit; + + return 0; + +OnErrorExit: + AFB_ApiError(source->api, "ProcessSndParams: sndcard=%s params: missing (rate|channel) params=%s", uid, json_object_get_string(paramsJ)); + return -1; +} + +STATIC int ProcessOneSndCard(CtlSourceT *source, json_object *sndcardJ, AlsaPcmInfoT *snd) { + json_object *sinkJ, *paramsJ=NULL; + int error; + + error = wrap_json_unpack(sndcardJ, "{ss,s?s,s?s,s?i,s?i,s?i,so,s?o !}", "uid",&snd->uid, "devpath",&snd->devpath, "cardid",&snd->cardid + , "cardidx",&snd->cardidx, "device",&snd->device, "subdev",&snd->subdev, "sink",&sinkJ, "params",¶msJ); + if (error || !snd->uid || !sinkJ || (!snd->devpath && !snd->cardid && snd->cardidx)) { + AFB_ApiNotice(source->api, "ProcessOneSndCard missing 'uid|path|cardid|cardidx|channels|device|subdev|numid|params' devin=%s", json_object_get_string(sndcardJ)); + goto OnErrorExit; + } + + if (paramsJ) error= ProcessSndParams(source, snd->uid, paramsJ, &snd->params); + if (error) { + AFB_ApiError(source->api, "ProcessOneSndCard: sndcard=%s invalid params=%s", snd->uid, json_object_get_string(paramsJ)); + goto OnErrorExit; + } + + + // check snd card is accessible + error = AlsaByPathDevid(source, snd); + if (error) { + AFB_ApiError(source->api, "ProcessOneSndCard: sndcard=%s not found config=%s", snd->uid, json_object_get_string(sndcardJ)); + goto OnErrorExit; + } + + // protect each sndcard with a dmix plugin to enable audio-stream mixing + AlsaPcmInfoT *dmixPcm= AlsaCreateDmix(source, snd->uid, snd); + if (!dmixPcm) { + AFB_ApiError(source->api, "ProcessOneSndCard: sndcard=%s fail to attach dmix plugin", snd->uid); + goto OnErrorExit; + } else { + snd_pcm_close(dmixPcm->handle); + snd->cardid=dmixPcm->cardid; + } + + switch (json_object_get_type(sinkJ)) { + case json_type_object: + snd->ccount=1; + snd->channels = calloc(snd->ccount+1, sizeof (AlsaPcmChannels)); + error = ProcessOneChannel(source, snd->uid, sndcardJ, &snd->channels[0]); + if (error) goto OnErrorExit; + break; + case json_type_array: + snd->ccount = (int)json_object_array_length(sinkJ); + snd->channels = calloc(snd->ccount+1, sizeof (AlsaPcmChannels)); + for (int idx = 0; idx < snd->ccount; idx++) { + json_object *channelJ = json_object_array_get_idx(sinkJ, idx); + error = ProcessOneChannel(source, snd->uid, channelJ, &snd->channels[idx]); + if (error) goto OnErrorExit; + } + break; + default: + AFB_ApiError(source->api, "ProcessOneSndCard:%s invalid sink=%s", snd->uid, json_object_get_string(sinkJ)); + goto OnErrorExit; + } + + return 0; + +OnErrorExit: + return -1; +} + +CTLP_LUA2C(snd_cards, source, argsJ, responseJ) { + AlsaPcmInfoT *sndcards; + + int error; + size_t count; + + switch (json_object_get_type(argsJ)) { + case json_type_object: + count= 1; + sndcards = calloc(count + 1, sizeof (AlsaPcmInfoT)); + error = ProcessOneSndCard(source, argsJ, &sndcards[0]); + if (error) goto OnErrorExit; + break; + case json_type_array: + count = json_object_array_length(argsJ); + sndcards = calloc(count + 1, sizeof (AlsaPcmInfoT)); + for (int idx = 0; idx < count; idx++) { + json_object *sndcardJ = json_object_array_get_idx(argsJ, idx); + error = ProcessOneSndCard(source, sndcardJ, &sndcards[idx]); + if (error) goto OnErrorExit; + } + break; + default: + AFB_ApiError(source->api, "L2C:sndcards: invalid argsJ= %s", json_object_get_string(argsJ)); + goto OnErrorExit; + } + + // register Sound card and multi when needed + Softmixer->sndcardCtl= sndcards; + + if (count == 1) { + + // only one sound card we multi would be useless + Softmixer->multiPcm = &sndcards[0]; + + } else { + AlsaPcmInfoT *pcmMulti; + + // instantiate an alsa multi plugin + pcmMulti = AlsaCreateMulti(source, "PcmMulti"); + if (!pcmMulti) goto OnErrorExit; + + // Close Multi and save into globak handle for further use + snd_pcm_close(pcmMulti->handle); + Softmixer->multiPcm= pcmMulti; + } + + return 0; + +OnErrorExit: + AFB_ApiNotice(source->api, "L2C:sndcards fail to process: %s", json_object_get_string(argsJ)); + return -1; +} diff --git a/plugins/alsa/alsa-api-sndloops.c b/plugins/alsa/alsa-api-sndloops.c new file mode 100644 index 0000000..5599ae0 --- /dev/null +++ b/plugins/alsa/alsa-api-sndloops.c @@ -0,0 +1,135 @@ +/* + * 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" +#include <string.h> + +// Fulup need to be cleanup with new controller version +extern Lua2cWrapperT Lua2cWrap; + +STATIC int ProcessOneSubdev(CtlSourceT *source, AlsaSndLoopT *loop, json_object *subdevJ, AlsaPcmInfoT *subdev) { + + int error = wrap_json_unpack(subdevJ, "{si,si !}", "subdev", &subdev->subdev, "numid", &subdev->numid); + if (error) { + AFB_ApiError(source->api, "ProcessOneSubdev: loop=%s missing (uid|subdev|numid) json=%s", loop->uid, json_object_get_string(subdevJ)); + goto OnErrorExit; + } + + // create a fake uid and complete subdev info from loop handle + char subuid[30]; + snprintf(subuid,sizeof(subuid),"loop:/%i/%i", subdev->subdev,subdev->numid); + subdev->uid = strdup(subuid); + subdev->device = loop->capture; // Fulup: with alsaloop softmixer only use capture device (playback is used by applications) + subdev->devpath = loop->devpath; + subdev->cardid = NULL; // force AlsaByPathDevId to rebuild a new one for each subdev + subdev->cardidx = loop->cardidx; + + // check if card exist + error = AlsaByPathDevid(source, subdev); + if (error) { + AFB_ApiError(source->api, "ProcessOneSubdev: loop=%s fail to open subdev=%s", loop->uid, json_object_get_string(subdevJ)); + goto OnErrorExit; + } + + return 0; + +OnErrorExit: + return -1; +} + +STATIC int ProcessOneLoop(CtlSourceT *source, json_object *loopJ, AlsaSndLoopT *loop) { + json_object *subdevsJ=NULL, *devicesJ=NULL; + int error; + + error = wrap_json_unpack(loopJ, "{ss,s?s,s?s,s?i,s?o,so}", "uid",&loop->uid, "devpath",&loop->devpath, "cardid",&loop->cardid + , "cardidx",&loop->cardidx, "devices",&devicesJ, "subdevs",&subdevsJ); + if (error || !loop->uid || !subdevsJ || (!loop->devpath && !loop->cardid && loop->cardidx)) { + AFB_ApiNotice(source->api, "ProcessOneLoop missing 'uid|devpath|cardid|cardidx|devices|subdevs' loop=%s", json_object_get_string(loopJ)); + goto OnErrorExit; + } + + // make sure useful information will not be removed + loop->uid=strdup(loop->uid); + if (loop->cardid) loop->cardid=strdup(loop->cardid); + if (loop->devpath) loop->cardid=strdup(loop->devpath); + + // Default devices is payback=0 capture=1 + if (!devicesJ) { + loop->playback=0; + loop->capture=1; + } else { + error = wrap_json_unpack(devicesJ, "{si,si}", "capture",&loop->capture, "playback", &loop->playback); + if (error) { + AFB_ApiNotice(source->api, "ProcessOneLoop=%s missing 'capture|playback' devices=%s", loop->uid, json_object_get_string(devicesJ)); + goto OnErrorExit; + } + } + + switch (json_object_get_type(subdevsJ)) { + case json_type_object: + loop->scount = 1; + loop->subdevs = calloc(loop->scount+1, sizeof (AlsaPcmInfoT)); + error = ProcessOneSubdev(source, loop, subdevsJ, &loop->subdevs[0]); + if (error) goto OnErrorExit; + break; + case json_type_array: + loop->scount = (int)json_object_array_length(subdevsJ); + loop->subdevs = calloc(loop->scount+1, sizeof (AlsaPcmInfoT)); + for (int idx = 0; idx < loop->scount; idx++) { + json_object *subdevJ = json_object_array_get_idx(subdevsJ, idx); + error = ProcessOneSubdev(source, loop, subdevJ, &loop->subdevs[idx]); + if (error) goto OnErrorExit; + } + break; + default: + AFB_ApiError(source->api, "L2C:ProcessOneLoop=%s invalid subdevs= %s", loop->uid, json_object_get_string(subdevsJ)); + goto OnErrorExit; + } + + return 0; + +OnErrorExit: + return -1; +} + +CTLP_LUA2C(snd_loops, source, argsJ, responseJ) { + int error; + AlsaSndLoopT *sndLoop = calloc (1, sizeof(AlsaSndLoopT)); + + if (json_object_get_type(argsJ) != json_type_object) { + AFB_ApiError(source->api, "L2C:sndloops: invalid object type= %s", json_object_get_string(argsJ)); + goto OnErrorExit; + } + + error = ProcessOneLoop(source, argsJ, sndLoop); + if (error) { + AFB_ApiError(source->api, "L2C:sndloops: invalid object= %s", json_object_get_string(argsJ)); + goto OnErrorExit; + } + + // register routed into global softmixer handle + Softmixer->loopCtl = sndLoop; + + + return 0; + +OnErrorExit: + return -1; +}
\ No newline at end of file diff --git a/plugins/alsa/alsa-api-sndstreams.c b/plugins/alsa/alsa-api-sndstreams.c new file mode 100644 index 0000000..9f5d90f --- /dev/null +++ b/plugins/alsa/alsa-api-sndstreams.c @@ -0,0 +1,161 @@ +/* + * 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" +#include <string.h> + +// Fulup need to be cleanup with new controller version +extern Lua2cWrapperT Lua2cWrap; + +STATIC int ProcessOneStream(CtlSourceT *source, json_object *streamJ, AlsaSndStreamT *stream) { + int error; + const char*format=NULL; + + error = wrap_json_unpack(streamJ, "{ss,ss,s?i,s?b,s?i,s?i,s?i !}", "uid", &stream->uid, "zone", &stream->zone, "volume", &stream->volume, "mute", stream->mute + , "rate", &stream->params.rate, "format", &format, "access", &stream->params.access); + if (error) { + AFB_ApiNotice(source->api, "ProcessOneStream missing 'uid|zone|volume|rate|mute' stream=%s", json_object_get_string(streamJ)); + goto OnErrorExit; + } + + if (!format) stream->params.format=SND_PCM_FORMAT_UNKNOWN; + else if (!strcasecmp (format, "S16_LE")) stream->params.format=SND_PCM_FORMAT_S16_LE; + else if (!strcasecmp (format, "S16_BE")) stream->params.format=SND_PCM_FORMAT_S16_BE; + else if (!strcasecmp (format, "U16_LE")) stream->params.format=SND_PCM_FORMAT_U16_LE; + else if (!strcasecmp (format, "U16_BE")) stream->params.format=SND_PCM_FORMAT_U16_BE; + else if (!strcasecmp (format, "S32_LE")) stream->params.format=SND_PCM_FORMAT_S32_LE; + else if (!strcasecmp (format, "S32_BE")) stream->params.format=SND_PCM_FORMAT_S32_BE; + else if (!strcasecmp (format, "U32_LE")) stream->params.format=SND_PCM_FORMAT_U32_LE; + else if (!strcasecmp (format, "U32_BE")) stream->params.format=SND_PCM_FORMAT_U32_BE; + else if (!strcasecmp (format, "S24_LE")) stream->params.format=SND_PCM_FORMAT_S24_LE; + else if (!strcasecmp (format, "S24_BE")) stream->params.format=SND_PCM_FORMAT_S24_BE; + else if (!strcasecmp (format, "U24_LE")) stream->params.format=SND_PCM_FORMAT_U24_LE; + else if (!strcasecmp (format, "U24_BE")) stream->params.format=SND_PCM_FORMAT_U24_BE; + else if (!strcasecmp (format, "S8")) stream->params.format=SND_PCM_FORMAT_S8; + else if (!strcasecmp (format, "U8")) stream->params.format=SND_PCM_FORMAT_U8; + else if (!strcasecmp (format, "FLOAT_LE")) stream->params.format=SND_PCM_FORMAT_FLOAT_LE; + else if (!strcasecmp (format, "FLOAT_BE")) stream->params.format=SND_PCM_FORMAT_FLOAT_LE; + else { + AFB_ApiNotice(source->api, "ProcessOneStream unsupported format 'uid|zone|volume|rate|mute' stream=%s", json_object_get_string(streamJ)); + goto OnErrorExit; + } + + if(!stream->params.rate) stream->params.rate=ALSA_DEFAULT_PCM_RATE; + + // make sure remain valid even when json object is removed + stream->uid = strdup(stream->uid); + stream->zone = strdup(stream->zone); + + return 0; + +OnErrorExit: + return -1; +} + +CTLP_LUA2C(snd_streams, source, argsJ, responseJ) { + AlsaSndStreamT *sndStream; + int error; + size_t count; + + // assert static/global softmixer handle get requited info + AlsaSndLoopT *ctlLoop = Softmixer->loopCtl; + if (!ctlLoop) { + AFB_ApiError(source->api, "L2C:sndstreams: No Loop found [should register snd_loop first]"); + goto OnErrorExit; + } + + switch (json_object_get_type(argsJ)) { + case json_type_object: + count = 1; + sndStream = calloc(count + 1, sizeof (AlsaSndStreamT)); + error = ProcessOneStream(source, argsJ, &sndStream[0]); + if (error) { + AFB_ApiError(source->api, "L2C:sndstreams: invalid stream= %s", json_object_get_string(argsJ)); + goto OnErrorExit; + } + break; + + case json_type_array: + count = json_object_array_length(argsJ); + sndStream = calloc(count + 1, sizeof (AlsaSndStreamT)); + for (int idx = 0; idx < count; idx++) { + json_object *sndStreamJ = json_object_array_get_idx(argsJ, idx); + error = ProcessOneStream(source, sndStreamJ, &sndStream[idx]); + if (error) { + AFB_ApiError(source->api, "sndstreams: invalid stream= %s", json_object_get_string(sndStreamJ)); + goto OnErrorExit; + } + } + break; + default: + AFB_ApiError(source->api, "L2C:sndstreams: invalid argsJ= %s", json_object_get_string(argsJ)); + goto OnErrorExit; + } + + // instantiate stream as a softvol + + for (int idx = 0; sndStream[idx].uid != NULL; idx++) { + + // Search for a free loop capture device + AFB_ApiNotice(source->api, "L2C:sndstreams stream=%s Start", (char*)sndStream[idx].uid); + ctlLoop->scount--; + if (ctlLoop->scount < 0) { + AFB_ApiError(source->api, "L2C:sndstreams no more subdev avaliable in loopback=%s", ctlLoop->uid); + goto OnErrorExit; + } + + // Retrieve subdev loop device and open corresponding pcm + AlsaPcmInfoT *devLoop = &ctlLoop->subdevs[ctlLoop->scount]; + AlsaPcmInfoT *pcmLoop = AlsaByPathOpenPcm(source, devLoop, SND_PCM_STREAM_CAPTURE); + if (!pcmLoop) goto OnErrorExit; + + AlsaPcmInfoT *streamPcm = AlsaCreateStream(source, &sndStream[idx], pcmLoop); + if (!streamPcm) { + AFB_ApiError(source->api, "L2C:sndstreams fail to create stream=%s", (char*) sndStream[idx].uid); + goto OnErrorExit; + } + + // capture stream inherit channel from targeted zone + pcmLoop->ccount = streamPcm->ccount; + sndStream[idx].params.channels=streamPcm->ccount; + + // start stream pcm copy + error = AlsaPcmCopy(source, pcmLoop, streamPcm, &sndStream[idx].params); + if (error) goto OnErrorExit; + + // Registration to event should be done after pcm_start + if (pcmLoop->numid) { + error = AlsaCtlRegister(source, pcmLoop, pcmLoop->numid); + if (error) goto OnErrorExit; + } + + + // Debug Alsa Config + //AlsaDumpElemConfig (source, "\n\nAlsa_Config\n------------\n", "pcm"); + AlsaDumpPcmInfo(source, "\n\nPcm_config\n-----------\n", streamPcm->handle); + + AFB_ApiNotice(source->api, "L2C:sndstreams stream=%s OK\n", (char*) sndStream[idx].uid); + } + + return 0; + +OnErrorExit: + return -1; +}
\ No newline at end of file diff --git a/plugins/alsa/alsa-api-sndzones.c b/plugins/alsa/alsa-api-sndzones.c new file mode 100644 index 0000000..11d77c2 --- /dev/null +++ b/plugins/alsa/alsa-api-sndzones.c @@ -0,0 +1,145 @@ +/* + * 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" +#include <string.h> + +// Fulup need to be cleanup with new controller version +extern Lua2cWrapperT Lua2cWrap; + +STATIC int ProcessOneChannel(CtlSourceT *source, const char* uid, json_object *channelJ, AlsaPcmChannels *channel) { + const char*channelUid; + + int error = wrap_json_unpack(channelJ, "{ss,si,s?i !}", "target", &channelUid, "channel", &channel->port); + if (error) goto OnErrorExit; + + channel->uid=strdup(channelUid); + return 0; + +OnErrorExit: + AFB_ApiError(source->api, "ProcessOneChannel: zone=%s channel: missing (target||channel) json=%s", uid, json_object_get_string(channelJ)); + return -1; +} + +STATIC int ProcessOneZone(CtlSourceT *source, json_object *zoneJ, AlsaSndZoneT *zone) { + json_object *mappingJ; + size_t count; + const char* streamType; + int error; + + error = wrap_json_unpack(zoneJ, "{ss,s?s,so !}", "uid", &zone->uid, "type", &streamType, "mapping", &mappingJ); + if (error) { + AFB_ApiNotice(source->api, "ProcessOneone missing 'uid|type|mapping' zone=%s", json_object_get_string(zoneJ)); + goto OnErrorExit; + } + + if (!streamType) zone->type = SND_PCM_STREAM_PLAYBACK; + else { + if (!strcasecmp(streamType, "capture")) zone->type = SND_PCM_STREAM_CAPTURE; + else if (!strcasecmp(streamType, "playback")) zone->type = SND_PCM_STREAM_PLAYBACK; + else { + AFB_ApiError(source->api, "ProcessOneZone:%s invalid stream type !(playback||capture) json=%s", zone->uid, json_object_get_string(zoneJ)); + goto OnErrorExit; + } + } + + // make sure remain valid even when json object is removed + zone->uid= strdup(zone->uid); + + switch (json_object_get_type(mappingJ)) { + case json_type_object: + count = 1; + zone->channels = calloc(count + 1, sizeof (AlsaPcmChannels)); + error = ProcessOneChannel(source, zone->uid, mappingJ, &zone->channels[0]); + if (error) goto OnErrorExit; + break; + case json_type_array: + count = json_object_array_length(mappingJ); + zone->channels = calloc(count + 1, sizeof (AlsaPcmChannels)); + for (int idx = 0; idx < count; idx++) { + json_object *channelJ = json_object_array_get_idx(mappingJ, idx); + error = ProcessOneChannel(source, zone->uid, channelJ, &zone->channels[idx]); + if (error) goto OnErrorExit; + } + break; + default: + AFB_ApiError(source->api, "ProcessOneZone:%s invalid mapping=%s", zone->uid, json_object_get_string(mappingJ)); + goto OnErrorExit; + } + + return 0; + +OnErrorExit: + return -1; +} + +CTLP_LUA2C(snd_zones, source, argsJ, responseJ) { + AlsaSndZoneT *sndZone; + int error; + size_t count; + + switch (json_object_get_type(argsJ)) { + case json_type_object: + count = 1; + sndZone = calloc(count + 1, sizeof (AlsaSndZoneT)); + error = ProcessOneZone(source, argsJ, &sndZone[0]); + if (error) { + AFB_ApiError(source->api, "L2C:sndzones: invalid zone= %s", json_object_get_string(argsJ)); + goto OnErrorExit; + } + break; + + case json_type_array: + count = json_object_array_length(argsJ); + sndZone = calloc(count + 1, sizeof (AlsaSndZoneT)); + for (int idx = 0; idx < count; idx++) { + json_object *sndZoneJ = json_object_array_get_idx(argsJ, idx); + error = ProcessOneZone(source, sndZoneJ, &sndZone[idx]); + if (error) { + AFB_ApiError(source->api, "L2C:sndzones: invalid zone= %s", json_object_get_string(sndZoneJ)); + goto OnErrorExit; + } + } + break; + default: + AFB_ApiError(source->api, "L2C:sndzones: invalid argsJ= %s", json_object_get_string(argsJ)); + goto OnErrorExit; + } + + // register routed into global softmixer handle + Softmixer->zonePcms = calloc(count+1, sizeof (AlsaPcmInfoT*)); + + // instantiate one route PCM per zone with multi plugin as slave + for (int idx = 0; sndZone[idx].uid != NULL; idx++) { + Softmixer->zonePcms[idx] = AlsaCreateRoute(source, &sndZone[idx]); + if (!Softmixer->zonePcms[idx]) { + AFB_ApiNotice(source->api, "L2C:sndzones fail to create route zone=%s", sndZone[idx].uid); + goto OnErrorExit; + } + snd_pcm_close(Softmixer->zonePcms[idx]->handle); + } + + // do not need this handle anymore + free (sndZone); + return 0; + +OnErrorExit: + return -1; +}
\ No newline at end of file diff --git a/plugins/alsa/alsa-core-ctl.c b/plugins/alsa/alsa-core-ctl.c index 5944fb8..38ce8cc 100644 --- a/plugins/alsa/alsa-core-ctl.c +++ b/plugins/alsa/alsa-core-ctl.c @@ -101,21 +101,21 @@ OnErrorExit: } -PUBLIC snd_ctl_t *AlsaCtlOpenCtl(CtlSourceT *source, const char *devid) { +PUBLIC snd_ctl_t *AlsaCtlOpenCtl(CtlSourceT *source, const char *cardid) { int error; snd_ctl_t *ctlDev; - if (devid) goto OnErrorExit; + if (cardid) goto OnErrorExit; - if ((error = snd_ctl_open(&ctlDev, devid, SND_CTL_READONLY)) < 0) { - devid = "Not Defined"; + if ((error = snd_ctl_open(&ctlDev, cardid, SND_CTL_READONLY)) < 0) { + cardid = "Not Defined"; goto OnErrorExit; } return ctlDev; OnErrorExit: - AFB_ApiError(source->api, "AlsaCtlOpenCtl: fail to find sndcard by id= %s", devid); + AFB_ApiError(source->api, "AlsaCtlOpenCtl: fail to find sndcard by id= %s", cardid); return NULL; } @@ -198,14 +198,14 @@ OnErrorExit: } // Clone of AlsaLib snd_card_load2 static function -PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(CtlSourceT *source, const char *devid) { +PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(CtlSourceT *source, const char *cardid) { int error; snd_ctl_t *ctlDev; - if (devid) goto OnErrorExit; + if (cardid) goto OnErrorExit; - if ((error = snd_ctl_open(&ctlDev, devid, SND_CTL_READONLY)) < 0) { - devid = "Not Defined"; + if ((error = snd_ctl_open(&ctlDev, cardid, SND_CTL_READONLY)) < 0) { + cardid = "Not Defined"; goto OnErrorExit; } @@ -216,7 +216,7 @@ PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(CtlSourceT *source, const char *devid return cardInfo; OnErrorExit: - AFB_ApiError(source->api, "AlsaCtlGetInfo: fail to find sndcard by id= %s", devid); + AFB_ApiError(source->api, "AlsaCtlGetInfo: fail to find sndcard by id= %s", cardid); return NULL; } @@ -245,6 +245,7 @@ STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, v snd_ctl_event_t *eventId; snd_ctl_elem_id_t *elemId; long value; + int idx; if ((revents & EPOLLHUP) != 0) { AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB hanghup [card:%s disconnected]", subscribeHandle->info); @@ -275,15 +276,20 @@ STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, v error = CtlElemIdGetNumid(subscribeHandle->api, subscribeHandle->ctlDev, elemId, &numid); if (error) goto OnErrorExit; - for (int idx = 0; idx < AudioStreamHandle.count; idx++) { + for (idx = 0; idx < AudioStreamHandle.count; idx++) { if (AudioStreamHandle.stream[idx].numid == numid) { - const char *pcmName = AudioStreamHandle.stream[idx].pcm->devid; + const char *pcmName = AudioStreamHandle.stream[idx].pcm->cardid; snd_pcm_pause(AudioStreamHandle.stream[idx].pcm->handle, !value); AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d pcm=%s pause=%d numid=%d", subscribeHandle->info, subscribeHandle->tid, pcmName, !value, numid); break; } } - + if (idx == AudioStreamHandle.count) { + char cardName[32]; + ALSA_CTL_UID(subscribeHandle->ctlDev,cardName); + AFB_ApiWarning(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d card=%s numid=%d (ignored)", subscribeHandle->info, subscribeHandle->tid, cardName, numid); + } + OnSuccessExit: return 0; @@ -345,7 +351,7 @@ PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t * ctlDev) { subscribeHandle->ctlDev = ctlDev; subscribeHandle->info = "ctlEvt"; - // subscribe for sndctl events attached to devid + // subscribe for sndctl events attached to cardid if ((error = snd_ctl_subscribe_events(ctlDev, 1)) < 0) { AFB_ApiError(source->api, "AlsaCtlSubscribe: fail sndcard=%s to subscribe events", ALSA_CTL_UID(ctlDev, string)); goto OnErrorExit; @@ -389,7 +395,7 @@ PUBLIC int AlsaCtlRegister(CtlSourceT *source, AlsaPcmInfoT *pcm, int numid) { // NumID are attached to sndcard retrieve ctldev from PCM snd_ctl_t* ctlDev = AlsaCrlFromPcm(source, pcm->handle); if (!ctlDev) { - AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail attache sndcard", pcm->devid); + AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail attache sndcard", pcm->cardid); goto OnErrorExit; } @@ -401,7 +407,7 @@ PUBLIC int AlsaCtlRegister(CtlSourceT *source, AlsaPcmInfoT *pcm, int numid) { error = AlsaCtlGetNumidValueI(source, ctlDev, numid, &value); if (error) goto OnErrorExit; - AFB_ApiNotice(source->api, "AlsaCtlRegister [pcm=%s] numid=%d value=%ld", pcm->devid, numid, value); + AFB_ApiNotice(source->api, "AlsaCtlRegister [pcm=%s] numid=%d value=%ld", pcm->cardid, numid, value); // store PCM in order to pause/resume depending on event int count=AudioStreamHandle.count; @@ -413,7 +419,7 @@ PUBLIC int AlsaCtlRegister(CtlSourceT *source, AlsaPcmInfoT *pcm, int numid) { // toggle pause/resume (should be done after pcm_start) if ((error = snd_pcm_pause(pcm->handle, !value)) < 0) { - AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail to pause", pcm->devid); + AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail to pause", pcm->cardid); goto OnErrorExit; } diff --git a/plugins/alsa/alsa-core-pcm.c b/plugins/alsa/alsa-core-pcm.c index a08dfca..f143eb6 100644 --- a/plugins/alsa/alsa-core-pcm.c +++ b/plugins/alsa/alsa-core-pcm.c @@ -91,13 +91,13 @@ PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *op if (!opts->access) opts->access = SND_PCM_ACCESS_RW_INTERLEAVED; error = snd_pcm_hw_params_set_access(pcm->handle, pxmHwParams, opts->access); if (error) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Interleave=%d mode error=%s", ALSA_PCM_UID(pcm->handle, string), opts->access, snd_strerror(error)); + AFB_ApiError(source->api, "AlsaPcmConf:%s Fail PCM=%s Set_Interleave=%d mode error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), opts->access, snd_strerror(error)); goto OnErrorExit; }; if (opts->format != SND_PCM_FORMAT_UNKNOWN) { if ((error = snd_pcm_hw_params_set_format(pcm->handle, pxmHwParams, opts->format)) < 0) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Format=%d error=%s", ALSA_PCM_UID(pcm->handle, string), opts->format, snd_strerror(error)); + AFB_ApiError(source->api, "AlsaPcmConf:%s Fail PCM=%s Set_Format=%d error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), opts->format, snd_strerror(error)); AlsaDumpFormats(source, pcm->handle); goto OnErrorExit; } @@ -106,27 +106,27 @@ PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *op if (opts->rate > 0) { unsigned int pcmRate = opts->rate; if ((error = snd_pcm_hw_params_set_rate_near(pcm->handle, pxmHwParams, &opts->rate, 0)) < 0) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Rate=%d error=%s", ALSA_PCM_UID(pcm->handle, string), opts->rate, snd_strerror(error)); + AFB_ApiError(source->api, "AlsaPcmConf:%s Fail PCM=%s Set_Rate=%d error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), opts->rate, snd_strerror(error)); goto OnErrorExit; } // check we got requested rate if (opts->rate != pcmRate) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Rate ask=%dHz get=%dHz", ALSA_PCM_UID(pcm->handle, string), pcmRate, opts->rate); + AFB_ApiError(source->api, "AlsaPcmConf:%s Fail PCM=%s Set_Rate ask=%dHz get=%dHz", pcm->uid, ALSA_PCM_UID(pcm->handle, string), pcmRate, opts->rate); goto OnErrorExit; } } if (opts->channels) { if ((error = snd_pcm_hw_params_set_channels(pcm->handle, pxmHwParams, opts->channels)) < 0) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Channels=%d error=%s", ALSA_PCM_UID(pcm->handle, string), opts->channels, snd_strerror(error)); + AFB_ApiError(source->api, "AlsaPcmConf:%s Fail PCM=%s Set_Channels=%d error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), opts->channels, snd_strerror(error)); goto OnErrorExit; }; } // store selected values if ((error = snd_pcm_hw_params(pcm->handle, pxmHwParams)) < 0) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s apply hwparams error=%s", ALSA_PCM_UID(pcm->handle, string), snd_strerror(error)); + AFB_ApiError(source->api, "AlsaPcmConf:%s Fail PCM=%s apply hwparams error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), snd_strerror(error)); goto OnErrorExit; } @@ -136,7 +136,7 @@ PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *op snd_pcm_hw_params_get_rate(pxmHwParams, &opts->rate, 0); opts->sampleSize = AlsaPeriodSize(opts->format); if (opts->sampleSize == 0) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s unsupported format format=%d", ALSA_PCM_UID(pcm->handle, string), opts->format); + AFB_ApiError(source->api, "AlsaPcmConf:%s Fail PCM=%s unsupported format format=%d", pcm->uid, ALSA_PCM_UID(pcm->handle, string), opts->format); goto OnErrorExit; } @@ -145,17 +145,17 @@ PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *op snd_pcm_sw_params_current(pcm->handle, pxmSwParams); if ((error = snd_pcm_sw_params_set_avail_min(pcm->handle, pxmSwParams, 16)) < 0) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail to PCM=%s set_buffersize error=%s", ALSA_PCM_UID(pcm->handle, string), snd_strerror(error)); + AFB_ApiError(source->api, "AlsaPcmConf:%s Fail to PCM=%s set_buffersize error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), snd_strerror(error)); goto OnErrorExit; }; // push software params into PCM if ((error = snd_pcm_sw_params(pcm->handle, pxmSwParams)) < 0) { - AFB_ApiError(source->api, "AlsaPcmConf: Fail to push software=%s params error=%s", ALSA_PCM_UID(pcm->handle, string), snd_strerror(error)); + AFB_ApiError(source->api, "AlsaPcmConf:%s Fail to push software=%s params error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), snd_strerror(error)); goto OnErrorExit; }; - AFB_ApiNotice(source->api, "AlsaPcmConf: PCM=%s channels=%d rate=%d format=%d access=%d done", ALSA_PCM_UID(pcm->handle,string), opts->channels, opts->rate, opts->format, opts->access); + AFB_ApiNotice(source->api, "AlsaPcmConf:%s PCM=%s channels=%d rate=%d format=%d access=%d done", pcm->uid, ALSA_PCM_UID(pcm->handle,string), opts->channels, opts->rate, opts->format, opts->access); return 0; OnErrorExit: diff --git a/plugins/alsa/alsa-plug-dmix.c b/plugins/alsa/alsa-plug-dmix.c index 3d67410..e28d009 100644 --- a/plugins/alsa/alsa-plug-dmix.c +++ b/plugins/alsa/alsa-plug-dmix.c @@ -28,14 +28,16 @@ ALSA_PLUG_PROTO(dmix); PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave) { snd_config_t *dmixConfig, *slaveConfig, *elemConfig, *pcmConfig; - AlsaPcmInfoT *pcmPlug= malloc(sizeof(AlsaPcmInfoT)); - pcmPlug->devid= pcmName; + AlsaPcmInfoT *pcmPlug= calloc(1,sizeof(AlsaPcmInfoT)); + pcmPlug->uid= strdup(pcmName); + pcmPlug->cardid=pcmPlug->uid; + int error=0; // refresh global alsalib config and create PCM top config snd_config_update(); error += snd_config_top(&dmixConfig); - error += snd_config_set_id (dmixConfig, pcmPlug->devid); + error += snd_config_set_id (dmixConfig, pcmPlug->cardid); error += snd_config_imake_string(&elemConfig, "type", "dmix"); error += snd_config_add(dmixConfig, elemConfig); error += snd_config_imake_integer(&elemConfig, "ipc_key", uniqueIpcIndex++); @@ -43,7 +45,11 @@ PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, Als if (error) goto OnErrorExit; error += snd_config_make_compound(&slaveConfig, "slave", 0); - error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->devid); + error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->cardid); + if (pcmSlave->params.rate) { + error += snd_config_add(slaveConfig, elemConfig); + error += snd_config_imake_integer(&elemConfig, "rate", pcmSlave->params.rate); + } error += snd_config_add(slaveConfig, elemConfig); if (error) goto OnErrorExit; @@ -51,26 +57,27 @@ PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, Als error += snd_config_add(dmixConfig, slaveConfig); if (error) goto OnErrorExit; - error = _snd_pcm_dmix_open(&pcmPlug->handle, pcmPlug->devid, snd_config, dmixConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); + error = _snd_pcm_dmix_open(&pcmPlug->handle, pcmPlug->cardid, snd_config, dmixConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); if (error) { - AFB_ApiError(source->api, "AlsaCreateDmix: fail to create DMIX=%s SLAVE=%s", pcmPlug->devid, pcmSlave->devid); + AFB_ApiError(source->api, "AlsaCreateDmix: fail to create Dmix=%s Slave=%s Error=%s", pcmPlug->cardid, pcmSlave->cardid, snd_strerror(error)); goto OnErrorExit; } error += snd_config_search(snd_config, "pcm", &pcmConfig); error += snd_config_add(pcmConfig, dmixConfig); if (error) { - AFB_ApiError(source->api, "AlsaCreateDmix: fail to add configDMIX=%s", pcmPlug->devid); + AFB_ApiError(source->api, "AlsaCreateDmix: fail to add configDMIX=%s", pcmPlug->cardid); goto OnErrorExit; } // Debug config & pcm - AlsaDumpCtlConfig (source, dmixConfig, 1); - //AlsaDumpPcmInfo(source, pcmPlug->handle, pcmPlug->devid); - AFB_ApiNotice(source->api, "AlsaCreateDmix: %s done", pcmPlug->devid); + AlsaDumpCtlConfig (source, "plug-dmix", dmixConfig, 1); + //AlsaDumpPcmInfo(source, pcmPlug->handle, pcmPlug->cardid); + AFB_ApiNotice(source->api, "AlsaCreateDmix: %s done\n", pcmPlug->cardid); return pcmPlug; OnErrorExit: - AFB_ApiNotice(source->api, "AlsaCreateDmix: OnErrorExit"); + AlsaDumpCtlConfig(source, "plug-dmix", dmixConfig, 1); + AFB_ApiNotice(source->api, "AlsaCreateDmix: OnErrorExit\n"); return NULL; }
\ No newline at end of file diff --git a/plugins/alsa/alsa-plug-multi.c b/plugins/alsa/alsa-plug-multi.c new file mode 100644 index 0000000..d6ef46e --- /dev/null +++ b/plugins/alsa/alsa-plug-multi.c @@ -0,0 +1,105 @@ +/* + * 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" + +ALSA_PLUG_PROTO(multi); + +PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmUid) { + + snd_config_t *multiConfig, *elemConfig, *slavesConfig, *slaveConfig, *bindingsConfig, *bindingConfig, *pcmConfig; + int error = 0, channelIdx=0; + AlsaPcmInfoT *pcmPlug = calloc(1, sizeof (AlsaPcmInfoT)); + pcmPlug->uid = pcmUid; + pcmPlug->cardid = pcmUid; + + AlsaPcmInfoT* pcmSlaves=Softmixer->sndcardCtl; + if (!Softmixer->sndcardCtl) { + AFB_ApiError(source->api, "AlsaCreateMulti: No Sound Card find [should register snd_cards first]"); + goto OnErrorExit; + } + + // refresh global alsalib config and create PCM top config + snd_config_update(); + error += snd_config_top(&multiConfig); + error += snd_config_set_id (multiConfig, pcmPlug->cardid); + error += snd_config_imake_string(&elemConfig, "type", "multi"); + error += snd_config_add(multiConfig, elemConfig); + if (error) goto OnErrorExit; + + error += snd_config_make_compound(&slavesConfig, "slaves", 0); + error += snd_config_make_compound(&bindingsConfig, "bindings", 0); + + // loop on sound card to include into multi + for (int idx = 0; pcmSlaves[idx].uid != NULL; idx++) { + AlsaPcmInfoT* sndcard=&pcmSlaves[idx]; + AlsaPcmChannels *channels = sndcard->channels; + + for (channelIdx=0; channels[channelIdx].uid != NULL; channelIdx++) { + char idxS[4]; // 999 channel should be more than enough + snprintf (idxS, sizeof(idxS), "%d", pcmPlug->ccount++); + // multi does not support to name channels + error += snd_config_make_compound(&bindingConfig,idxS, 0); + error += snd_config_imake_string(&elemConfig, "slave", sndcard->cardid); + error += snd_config_add(bindingConfig, elemConfig); + error += snd_config_imake_integer(&elemConfig,"channel", channelIdx); + error += snd_config_add(bindingConfig, elemConfig); + error += snd_config_add(bindingsConfig, bindingConfig); + } + + error += snd_config_make_compound(&slaveConfig, sndcard->uid, 0); + error += snd_config_imake_string(&elemConfig, "pcm", sndcard->cardid); + error += snd_config_add(slaveConfig, elemConfig); + error += snd_config_imake_integer(&elemConfig, "channels", channelIdx); + error += snd_config_add(slaveConfig, elemConfig); + error += snd_config_add(slavesConfig, slaveConfig); + } + + error += snd_config_add(multiConfig, slavesConfig); + error += snd_config_add(multiConfig, bindingsConfig); + if (error) goto OnErrorExit; + + // update top config to access previous plugin PCM + snd_config_update(); + + error = _snd_pcm_multi_open(&pcmPlug->handle, pcmUid, snd_config, multiConfig, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (error) { + AFB_ApiError(source->api, "AlsaCreateMulti: fail to create Plug=%s Error=%s", pcmPlug->cardid, snd_strerror(error)); + goto OnErrorExit; + } + + error += snd_config_search(snd_config, "pcm", &pcmConfig); + error += snd_config_add(pcmConfig, multiConfig); + if (error) { + AFB_ApiError(source->api, "AlsaCreateDmix: fail to add configDMIX=%s", pcmPlug->cardid); + goto OnErrorExit; + } + + // Debug config & pcm + //AlsaDumpCtlConfig(source, "plug-multi", multiConfig, 1); + //AlsaDumpPcmInfo(source, pcmPlug->handle, "pcmPlug->handle"); + AFB_ApiNotice(source->api, "AlsaCreateMulti: %s done\n", pcmPlug->cardid); + return pcmPlug; + +OnErrorExit: + AlsaDumpCtlConfig(source, "plug-multi", multiConfig, 1); + AFB_ApiNotice(source->api, "AlsaCreateMulti: OnErrorExit\n"); + return NULL; +}
\ No newline at end of file diff --git a/plugins/alsa/alsa-plug-route.c b/plugins/alsa/alsa-plug-route.c new file mode 100644 index 0000000..dc75b56 --- /dev/null +++ b/plugins/alsa/alsa-plug-route.c @@ -0,0 +1,140 @@ +/* + * 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" + +ALSA_PLUG_PROTO(route); + +STATIC int CardChannelByUid(CtlSourceT *source, AlsaPcmInfoT *pcmBackend, const char *uid) { + int channelIdx = -1; + + // search for channel within all sound card backend (channel port target is computed by order) + int targetIdx=0; + for (int cardIdx = 0; pcmBackend[cardIdx].uid != NULL; cardIdx++) { + AlsaPcmChannels *channels = pcmBackend[cardIdx].channels; + if (!channels) { + AFB_ApiError(source->api, "CardChannelByUid: No Backend card=%s [should declare channels]", pcmBackend[cardIdx].uid); + goto OnErrorExit; + } + + for (int idx = 0; channels[idx].uid != NULL; idx++) { + if (!strcmp(channels[idx].uid, uid)) return targetIdx; + targetIdx++; + } + } + + // this is OnErrorExit + return channelIdx; + +OnErrorExit: + return -1; +} + +PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone) { + + snd_config_t *routeConfig, *elemConfig, *slaveConfig, *tableConfig, *pcmConfig; + int error = 0; + AlsaPcmInfoT *pcmPlug = calloc(1, sizeof (AlsaPcmInfoT)); + pcmPlug->uid = zone->uid; + pcmPlug->cardid = zone->uid; + + AlsaPcmInfoT *pcmBackend = Softmixer->sndcardCtl; + AlsaPcmInfoT* pcmSlave=Softmixer->multiPcm; + if (!pcmBackend || !pcmSlave) { + AFB_ApiError(source->api, "AlsaCreateRoute:zone(%s)(zone) No Sound Card Ctl find [should register snd_cards first]", zone->uid); + goto OnErrorExit; + } + + // refresh global alsalib config and create PCM top config + snd_config_update(); + error += snd_config_top(&routeConfig); + error += snd_config_set_id(routeConfig, pcmPlug->cardid); + error += snd_config_imake_string(&elemConfig, "type", "route"); + error += snd_config_add(routeConfig, elemConfig); + error += snd_config_make_compound(&slaveConfig, "slave", 0); + error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->cardid); + error += snd_config_add(slaveConfig, elemConfig); + error += snd_config_imake_integer(&elemConfig, "channels", pcmSlave->ccount); + error += snd_config_add(slaveConfig, elemConfig); + error += snd_config_add(routeConfig, slaveConfig); + error += snd_config_make_compound(&tableConfig, "ttable", 0); + error += snd_config_add(routeConfig, tableConfig); + if (error) goto OnErrorExit; + + // tempry store to unable multiple channel to route to the same port + snd_config_t **cports; + cports = alloca(sizeof (snd_config_t*)*(pcmSlave->ccount + 1)); + memset(cports, 0, (sizeof (snd_config_t*)*(pcmSlave->ccount + 1))); + + // loop on sound card to include into multi + for (int idx = 0; zone->channels[idx].uid != NULL; idx++) { + + int target = CardChannelByUid(source, pcmBackend, zone->channels[idx].uid); + if (target < 0) { + AFB_ApiError(source->api, "AlsaCreateRoute:zone(%s) fail to find channel=%s", zone->uid, zone->channels[idx].uid); + goto OnErrorExit; + } + + int channel = zone->channels[idx].port; + double volume = 1.0; // currently only support 100% + + // if channel entry does not exit into ttable create it now + if (!cports[channel]) { + pcmPlug->ccount++; + char channelS[4]; // 999 channel should be more than enough + snprintf(channelS, sizeof (channelS), "%d", channel); + error += snd_config_make_compound(&cports[channel], channelS, 0); + error += snd_config_add(tableConfig, cports[channel]); + } + + // ttable require target port as a table and volume as a value + char targetS[4]; + snprintf(targetS, sizeof (targetS), "%d", target); + error += snd_config_imake_real(&elemConfig, targetS, volume); + error += snd_config_add(cports[channel], elemConfig); + if (error) goto OnErrorExit; + } + + // update top config to access previous plugin PCM + snd_config_update(); + + error = _snd_pcm_route_open(&pcmPlug->handle, zone->uid, snd_config, routeConfig, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (error) { + AFB_ApiError(source->api, "AlsaCreateRoute:zone(%s) fail to create Plug=%s error=%s", zone->uid, pcmPlug->cardid, snd_strerror(error)); + goto OnErrorExit; + } + + error += snd_config_search(snd_config, "pcm", &pcmConfig); + error += snd_config_add(pcmConfig, routeConfig); + if (error) { + AFB_ApiError(source->api, "AlsaCreateDmix:%s fail to add configDMIX=%s", zone->uid, pcmPlug->cardid); + goto OnErrorExit; + } + + // Debug config & pcm + AlsaDumpCtlConfig(source, "plug-route", routeConfig, 1); + AFB_ApiNotice(source->api, "AlsaCreateRoute:zone(%s) done\n", zone->uid); + return pcmPlug; + +OnErrorExit: + AlsaDumpCtlConfig(source, "plug-route", routeConfig, 1); + AFB_ApiNotice(source->api, "AlsaCreateRoute:zone(%s) OnErrorExit\n", zone->uid); + return NULL; +}
\ No newline at end of file diff --git a/plugins/alsa/alsa-plug-stream.c b/plugins/alsa/alsa-plug-stream.c new file mode 100644 index 0000000..fccb309 --- /dev/null +++ b/plugins/alsa/alsa-plug-stream.c @@ -0,0 +1,121 @@ +/* + * 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" + +ALSA_PLUG_PROTO(softvol); // stream uses solftvol plugin + +STATIC AlsaPcmInfoT* SlaveZoneByUid(CtlSourceT *source, AlsaPcmInfoT **pcmZones, const char *uid) { + AlsaPcmInfoT *slaveZone= NULL; + + // Loop on every registered zone pcm and extract (cardid) from (uid) + for (int idx = 0; pcmZones[idx] != NULL; idx++) { + if (!strcasecmp (pcmZones[idx]->uid, uid)) { + slaveZone= pcmZones[idx]; + return slaveZone; + } + } + + return NULL; +} + +PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *ctlControl) { + + snd_config_t *streamConfig, *elemConfig, *slaveConfig, *controlConfig,*pcmConfig; + int error = 0; + AlsaPcmInfoT *pcmPlug= malloc(sizeof(AlsaPcmInfoT)); + + // assert static/global softmixer handle get requited info + AlsaSndLoopT *ctlLoop = Softmixer->loopCtl; + if (!ctlLoop) { + AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) No Loop found [should register snd_loop first]",stream->uid); + goto OnErrorExit; + } + + // assert static/global softmixer handle get requited info + AlsaPcmInfoT **pcmZones = Softmixer->zonePcms; + if (!pcmZones) { + AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) No Zone found [should register snd_zones first]", stream->uid); + goto OnErrorExit; + } + + // search for target zone uid + pcmPlug->uid= stream->uid; + pcmPlug->cardid= stream->uid; + AlsaPcmInfoT *pcmSlave= SlaveZoneByUid (source, pcmZones, stream->zone); + if (!pcmSlave || !pcmSlave->uid) { + AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) fail to find Zone=%s", pcmPlug->uid, stream->zone); + goto OnErrorExit; + } + + // stream inherit from zone channel count + pcmPlug->ccount= pcmSlave->ccount; + + // refresh global alsalib config and create PCM top config + snd_config_update(); + error += snd_config_top(&streamConfig); + error += snd_config_set_id (streamConfig, pcmPlug->cardid); + error += snd_config_imake_string(&elemConfig, "type", "softvol"); + error += snd_config_add(streamConfig, elemConfig); + if (error) goto OnErrorExit; + + // add slave leaf + error += snd_config_make_compound(&slaveConfig, "slave", 0); + error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->cardid); + error += snd_config_add(slaveConfig, elemConfig); + error += snd_config_add(streamConfig, slaveConfig); + if (error) goto OnErrorExit; + + // add control leaf + error += snd_config_make_compound(&controlConfig, "control", 0); + error += snd_config_imake_string(&elemConfig, "name", stream->uid); + error += snd_config_add(controlConfig, elemConfig); + error += snd_config_imake_integer(&elemConfig, "card", ctlControl->cardidx); + error += snd_config_add(controlConfig, elemConfig); + error += snd_config_add(streamConfig, controlConfig); + if (error) goto OnErrorExit; + + // update top config to access previous plugin PCM + snd_config_update(); + + error = _snd_pcm_softvol_open(&pcmPlug->handle, stream->uid, snd_config, streamConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); + if (error) { + AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) fail to create Plug=%s Slave=%s error=%s", stream->uid, pcmPlug->cardid, pcmSlave->cardid, snd_strerror(error)); + goto OnErrorExit; + } + + error += snd_config_search(snd_config, "pcm", &pcmConfig); + error += snd_config_add(pcmConfig, streamConfig); + if (error) { + AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) fail to add config", stream->uid); + goto OnErrorExit; + } + + // Debug config & pcm + AlsaDumpCtlConfig (source, "plug-stream", streamConfig, 1); + //AlsaDumpPcmInfo(source, pcmPlug->handle, "pcmPlug->handle"); + AFB_ApiNotice(source->api, "AlsaCreateStream:%s(stream) done\n", stream->uid); + return pcmPlug; + +OnErrorExit: + AlsaDumpCtlConfig(source, "plug-stream", streamConfig, 1); + AFB_ApiNotice(source->api, "AlsaCreateStream:%s(stream) OnErrorExit\n", stream->uid); + return NULL; +}
\ No newline at end of file diff --git a/plugins/alsa/alsa-plug-vol.c b/plugins/alsa/alsa-plug-vol.c deleted file mode 100644 index 6de4ddf..0000000 --- a/plugins/alsa/alsa-plug-vol.c +++ /dev/null @@ -1,77 +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" - -ALSA_PLUG_PROTO(softvol); - -PUBLIC AlsaPcmInfoT* AlsaCreateVol(CtlSourceT *source, const char *pcmName, AlsaPcmInfoT* ctlTarget, AlsaPcmInfoT* pcmSlave) { - - snd_config_t *volConfig, *elemConfig, *slaveConfig, *controlConfig,*pcmConfig; - int error = 0; - AlsaPcmInfoT *pcmPlug= malloc(sizeof(AlsaPcmInfoT)); - pcmPlug->devid= pcmName; - - // refresh global alsalib config and create PCM top config - snd_config_update(); - error += snd_config_top(&volConfig); - - // add slave leaf - error += snd_config_make_compound(&slaveConfig, "slave", 0); - error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->devid); - error += snd_config_add(slaveConfig, elemConfig); - error += snd_config_add(volConfig, slaveConfig); - if (error) goto OnErrorExit; - - // add control leaf - error += snd_config_make_compound(&controlConfig, "control", 0); - error += snd_config_imake_string(&elemConfig, "name", pcmName); - error += snd_config_add(controlConfig, elemConfig); - error += snd_config_imake_integer(&elemConfig, "card", ctlTarget->cardid); - error += snd_config_add(controlConfig, elemConfig); - error += snd_config_add(volConfig, controlConfig); - if (error) goto OnErrorExit; - - // update top config to access previous plugin PCM - snd_config_update(); - - error = _snd_pcm_softvol_open(&pcmPlug->handle, pcmName, snd_config, volConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); - if (error) { - AFB_ApiError(source->api, "AlsaCreateVol: fail to create Plug=%s Slave=%s", pcmPlug->devid, pcmSlave->devid); - goto OnErrorExit; - } - - error += snd_config_search(snd_config, "pcm", &pcmConfig); - error += snd_config_add(pcmConfig, volConfig); - if (!error) { - AFB_ApiError(source->api, "AlsaCreateDmix: fail to add configDMIX=%s", pcmPlug->devid); - goto OnErrorExit; - } - - // Debug config & pcm - AlsaDumpCtlConfig (source, volConfig, 1); - //AlsaDumpPcmInfo(source, pcmPlug->handle, "pcmPlug->handle"); - AFB_ApiNotice(source->api, "AlsaCreateVol: %s done", pcmPlug->devid); - return pcmPlug; - -OnErrorExit: - AFB_ApiNotice(source->api, "AlsaCreateVol: OnErrorExit"); - return NULL; -}
\ No newline at end of file diff --git a/plugins/alsa/alsa-softmixer.c b/plugins/alsa/alsa-softmixer.c index 9586fa3..bd9ee5e 100644 --- a/plugins/alsa/alsa-softmixer.c +++ b/plugins/alsa/alsa-softmixer.c @@ -21,12 +21,16 @@ #include "alsa-softmixer.h" // Force Lua2cWrapper inclusion within already existing plugin + CTLP_LUA_REGISTER("alsa-mixer") +SoftMixerHandleT *Softmixer; + // Call at initialisation time CTLP_ONLOAD(plugin, callbacks) { - AFB_ApiDebug (plugin->api, "SoftMixer plugin: uid='%s' 'info='%s'", plugin->uid, plugin->info); - return NULL; + AFB_ApiDebug(plugin->api, "SoftMixer plugin: uid='%s' 'info='%s'", plugin->uid, plugin->info); + Softmixer = calloc(1, sizeof(SoftMixerHandleT)); + return NULL; } CTLP_LUA2C(AlsaDmix, source, argsJ, responseJ) { @@ -40,75 +44,74 @@ CTLP_LUA2C(AlsaDmix, source, argsJ, responseJ) { } + CTLP_LUA2C(AlsaRouter, source, argsJ, responseJ) { - json_object *sndInJ, *sndOutJ, *paramsJ=NULL; + json_object *sndInJ, *sndOutJ, *paramsJ = NULL; AlsaPcmInfoT *sndIn, *sndOut; int error; - + // make sndIn/Out a pointer to get cleaner code - sndIn=calloc(1, sizeof(AlsaPcmInfoT)); - sndOut=calloc(1, sizeof(AlsaPcmInfoT)); - + sndIn = calloc(1, sizeof (AlsaPcmInfoT)); + sndOut = calloc(1, sizeof (AlsaPcmInfoT)); + // set pcm options to defaults AlsaPcmHwInfoT *pcmOpts; - pcmOpts=calloc(1, sizeof(AlsaPcmHwInfoT)); - pcmOpts->format=SND_PCM_FORMAT_UNKNOWN; - pcmOpts->access=SND_PCM_ACCESS_RW_INTERLEAVED; - - error= wrap_json_unpack(argsJ, "{s:o,s:o,s?o}", "devin", &sndInJ, "devout", &sndOutJ, "params", ¶msJ); + pcmOpts = calloc(1, sizeof (AlsaPcmHwInfoT)); + pcmOpts->format = SND_PCM_FORMAT_UNKNOWN; + pcmOpts->access = SND_PCM_ACCESS_RW_INTERLEAVED; + + error = wrap_json_unpack(argsJ, "{s:o,s:o,s?o}", "devin", &sndInJ, "devout", &sndOutJ, "params", ¶msJ); if (error) { AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter ARGS missing devIn|devOut args=%s", json_object_get_string(argsJ)); goto OnErrorExit; } - error= wrap_json_unpack(sndInJ, "{s?s,s?s,s?i,s?i,s?i}", "path",&sndIn->devpath, "id",&sndIn->devid, "numid",&sndIn->numid, "dev",&sndIn->device, "sub",&sndIn->subdev); - if (error || (!sndIn->devpath && !sndIn->devid)) { + error = wrap_json_unpack(sndInJ, "{s?s,s?s,s?i,s?i,s?i}", "path", &sndIn->devpath, "id", &sndIn->cardid, "numid", &sndIn->numid, "dev", &sndIn->device, "sub", &sndIn->subdev); + if (error || (!sndIn->devpath && !sndIn->cardid)) { AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter DEV-IN missing 'path|id|dev|sub|numid' devin=%s", json_object_get_string(sndInJ)); goto OnErrorExit; } - - error= wrap_json_unpack(sndOutJ, "{s?s,s?s,s?i,s?i, s?i}", "path",&sndOut->devpath, "id",&sndOut->devid, "numid",&sndOut->numid,"dev",&sndOut->device, "sub",&sndOut->subdev); - if (error || (!sndOut->devpath && !sndOut->devid)) { + + error = wrap_json_unpack(sndOutJ, "{s?s,s?s,s?i,s?i, s?i}", "path", &sndOut->devpath, "id", &sndOut->cardid, "numid", &sndOut->numid, "dev", &sndOut->device, "sub", &sndOut->subdev); + if (error || (!sndOut->devpath && !sndOut->cardid)) { AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter DEV-OUT missing 'path|id|dev|sub' devout=%s", json_object_get_string(sndOutJ)); goto OnErrorExit; } - - if (paramsJ) if ((error= wrap_json_unpack(paramsJ, "{s?i, s?i, s?i, s?i}", "format", &pcmOpts->format, "access", &pcmOpts->access, "rate", &pcmOpts->rate, "channels",&pcmOpts->channels)) != 0) { - AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter PARAMS missing 'format|access|rate|channels' params=%s", json_object_get_string(paramsJ)); - goto OnErrorExit; - } - + + if (paramsJ) if ((error = wrap_json_unpack(paramsJ, "{s?i, s?i, s?i, s?i}", "format", &pcmOpts->format, "access", &pcmOpts->access, "rate", &pcmOpts->rate, "channels", &pcmOpts->channels)) != 0) { + AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter PARAMS missing 'format|access|rate|channels' params=%s", json_object_get_string(paramsJ)); + goto OnErrorExit; + } + AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter devin=%s devout=%s rate=%d channel=%d", sndIn->devpath, sndOut->devpath, pcmOpts->rate, pcmOpts->channels); - - // Check sndOut Exist and build a valid devid config - error= AlsaByPathDevid (source, sndOut); + + // Check sndOut Exist and build a valid cardid config + error = AlsaByPathDevid(source, sndOut); if (error) goto OnErrorExit; - - //AlsaPcmInfoT *pcmOut = AlsaByPathOpenPcm(source, sndOut, SND_PCM_STREAM_PLAYBACK); - + // open capture PCM AlsaPcmInfoT *pcmIn = AlsaByPathOpenPcm(source, sndIn, SND_PCM_STREAM_CAPTURE); if (!pcmIn) goto OnErrorExit; - - AlsaPcmInfoT *pcmDmix= AlsaCreateDmix(source, "DmixPlugPcm", sndOut); - if(!pcmDmix) goto OnErrorExit; - - AlsaPcmInfoT *pcmVol= AlsaCreateVol(source, "SoftVol", sndIn, pcmDmix); - if(!pcmVol) goto OnErrorExit; - - error = AlsaPcmCopy(source, pcmIn, pcmVol, pcmOpts); - if(error) goto OnErrorExit; - + + AlsaPcmInfoT *pcmDmix = AlsaCreateDmix(source, "DmixPlugPcm", sndOut); + if (!pcmDmix) goto OnErrorExit; + + //AlsaPcmInfoT *pcmVol = AlsaCreateVol(source, "SoftVol", sndIn, pcmDmix); + //if (!pcmVol) goto OnErrorExit; + + //error = AlsaPcmCopy(source, pcmIn, pcmVol, pcmOpts); + //if (error) goto OnErrorExit; + // Registration to event should be done after pcm_start if (sndIn->numid) { - error= AlsaCtlRegister(source, pcmIn, sndIn->numid); - if(error) goto OnErrorExit; - } - + error = AlsaCtlRegister(source, pcmIn, sndIn->numid); + if (error) goto OnErrorExit; + } + return 0; - + OnErrorExit: AFB_ApiNotice(source->api, "--lua2c-- ERROR AlsaRouter sndIn=%s sndOut=%s rate=%d channel=%d", sndIn->devpath, sndOut->devpath, pcmOpts->rate, pcmOpts->channels); - return -1; + return -1; } diff --git a/plugins/alsa/alsa-softmixer.h b/plugins/alsa/alsa-softmixer.h index 17e7cf2..db499c3 100644 --- a/plugins/alsa/alsa-softmixer.h +++ b/plugins/alsa/alsa-softmixer.h @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * -*/ + */ #define _GNU_SOURCE // needed for vasprintf @@ -25,6 +25,7 @@ #include <systemd/sd-event.h> #include <json-c/json_object.h> #include <stdio.h> +#include <stdlib.h> #include "ctl-plugin.h" #include "wrap-json.h" @@ -33,59 +34,114 @@ #define MAINLOOP_WATCHDOG 30000 #define MAX_AUDIO_STREAMS 8 +#define ALSA_DEFAULT_PCM_RATE 48000 +#define ALSA_CARDID_MAX_LEN 32 + #define ALSA_PLUG_PROTO(plugin) \ int _snd_pcm_ ## plugin ## _open(snd_pcm_t **pcmp, const char *name, snd_config_t *root, snd_config_t *conf, snd_pcm_stream_t stream, int mode) + + // alsa-utils-bypath.c typedef struct { + const char*uid; + int port; +} AlsaPcmChannels; + +typedef struct { + unsigned int rate; + unsigned int channels; + snd_pcm_format_t format; + snd_pcm_access_t access; + size_t sampleSize; +} AlsaPcmHwInfoT; + +typedef struct { + const char *uid; const char *devpath; - const char *devid; + const char *cardid; + int cardidx; int device; int subdev; int numid; - int cardid; + int ccount; snd_pcm_t *handle; + AlsaPcmChannels *channels; + AlsaPcmHwInfoT params; } AlsaPcmInfoT; typedef struct { - unsigned int rate; - unsigned int channels; - snd_pcm_format_t format; - snd_pcm_access_t access; - size_t sampleSize; -} AlsaPcmHwInfoT; + const char *uid; + snd_pcm_stream_t type; + AlsaPcmChannels *channels; + AlsaPcmInfoT *pcm; +} AlsaSndZoneT; + +typedef struct { + const char *uid; + const char *devpath; + const char *cardid; + int cardidx; + int playback; + int capture; + int scount; + AlsaPcmInfoT *subdevs; +} AlsaSndLoopT; + + -PUBLIC snd_ctl_card_info_t* AlsaByPathInfo (CtlSourceT *source, const char *control); +typedef struct { + const char *uid; + const char *zone; + int volume; + int mute; + AlsaPcmInfoT *pcm; + AlsaPcmHwInfoT params; +} AlsaSndStreamT; + +typedef struct { + AlsaSndLoopT *loopCtl; + AlsaPcmInfoT *sndcardCtl; + AlsaPcmInfoT *multiPcm; + AlsaPcmInfoT **zonePcms; +} SoftMixerHandleT; + +extern SoftMixerHandleT *Softmixer; + +PUBLIC snd_ctl_card_info_t* AlsaByPathInfo(CtlSourceT *source, const char *control); PUBLIC AlsaPcmInfoT* AlsaByPathOpenPcm(CtlSourceT *source, AlsaPcmInfoT *dev, snd_pcm_stream_t direction); -PUBLIC snd_ctl_t *AlsaByPathOpenCtl (CtlSourceT *source, AlsaPcmInfoT *dev); -PUBLIC int AlsaByPathDevid (CtlSourceT *source, AlsaPcmInfoT *dev); +PUBLIC snd_ctl_t *AlsaByPathOpenCtl(CtlSourceT *source, AlsaPcmInfoT *dev); +PUBLIC int AlsaByPathDevid(CtlSourceT *source, AlsaPcmInfoT *dev); // alsa-utils-dump.c PUBLIC void AlsaDumpFormats(CtlSourceT *source, snd_pcm_t *pcmHandle); PUBLIC char *AlsaDumpPcmUid(snd_pcm_t *pcmHandle, char *buffer, size_t len); PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle); -PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, snd_pcm_t *pcm, const char* info); +PUBLIC void AlsaDumpElemConfig(CtlSourceT *source, const char* info, const char* elem); +PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, const char* info, snd_pcm_t *pcm); PUBLIC void AlsaDumpPcmParams(CtlSourceT *source, snd_pcm_hw_params_t *pcmHwParams); -PUBLIC void AlsaDumpCtlConfig(CtlSourceT *source, snd_config_t *config, int indent); +PUBLIC void AlsaDumpCtlConfig(CtlSourceT *source, const char* info, snd_config_t *config, int indent); #define ALSA_PCM_UID(pcmHandle, buffer) AlsaDumpPcmUid(pcmHandle, buffer, sizeof(buffer)) PUBLIC char *AlsaDumpCtlUid(snd_ctl_t *ctlHandle, char *buffer, size_t len); #define ALSA_CTL_UID(ctlHandle, buffer) AlsaDumpCtlUid(ctlHandle, buffer, sizeof(buffer)) // alsa-core-ctl.c -PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo (CtlSourceT *source, const char *devid); -PUBLIC snd_ctl_t *AlsaCtlOpenCtl (CtlSourceT *source, const char *devid); +PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(CtlSourceT *source, const char *cardid); +PUBLIC snd_ctl_t *AlsaCtlOpenCtl(CtlSourceT *source, const char *cardid); PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t *ctlDev); PUBLIC int AlsaCtlRegister(CtlSourceT *source, AlsaPcmInfoT *pcm, int numid); PUBLIC int AlsaCtlGetNumidValueI(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long* value); - - +// alsa-core-pcm.c PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *opts); PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pcmOut, AlsaPcmHwInfoT *opts); -// _snd_pcm_PLUGIN_open_ see macro ALSA_PLUG_PROTO(plugin) +// alsa-plug-*.c _snd_pcm_PLUGIN_open_ see macro ALSA_PLUG_PROTO(plugin) PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave); -PUBLIC AlsaPcmInfoT* AlsaCreateVol(CtlSourceT *source, const char *pcmName, AlsaPcmInfoT* ctlTarget, AlsaPcmInfoT* pcmSlave); +PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmName); +PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *pcmControl); +PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone); + #endif
\ No newline at end of file diff --git a/plugins/alsa/alsa-utils-bypath.c b/plugins/alsa/alsa-utils-bypath.c index 8fba1f0..7ceb66f 100644 --- a/plugins/alsa/alsa-utils-bypath.c +++ b/plugins/alsa/alsa-utils-bypath.c @@ -62,25 +62,39 @@ PUBLIC int AlsaByPathDevid(CtlSourceT *source, AlsaPcmInfoT *dev) { // get card info from /dev/snd/xxx if not use hw:x,x,x snd_ctl_card_info_t *cardInfo = NULL; - if (dev->devpath) cardInfo = AlsaByPathInfo(source, dev->devpath); - else if (dev->devid) cardInfo = AlsaCtlGetInfo(source, dev->devid); + if (dev->devpath) { + cardInfo = AlsaByPathInfo(source, dev->devpath); + dev->cardid=NULL; + } + else if (dev->cardid) { + dev->cardid= strdup(dev->cardid); + cardInfo = AlsaCtlGetInfo(source, dev->cardid); + } + else { + dev->cardid=malloc(ALSA_CARDID_MAX_LEN); + snprintf((char*)dev->cardid, ALSA_CARDID_MAX_LEN, "hw:%i", dev->cardidx); + cardInfo = AlsaCtlGetInfo(source, dev->cardid); + cardInfo = AlsaCtlGetInfo(source, dev->cardid); + } if (!cardInfo) { - AFB_ApiWarning(source->api, "AlsaByPathOpenPcm: fail to find sndcard by path=%s id=%s", dev->devpath, dev->devid); + AFB_ApiWarning(source->api, "AlsaByPathOpenPcm: fail to find sndcard by path=%s id=%s", dev->devpath, dev->cardid); goto OnErrorExit; } // extract useful info from cardInfo handle - dev->cardid = snd_ctl_card_info_get_card(cardInfo); - - // if not provided build a valid PCM devid - if (!dev->devid) { - #define DEVID_MAX_LEN 32 - dev->devid=malloc(DEVID_MAX_LEN); - if (dev->subdev) snprintf((char*)dev->devid, DEVID_MAX_LEN, "hw:%i,%i,%i", dev->cardid, dev->device, dev->subdev); - else if (dev->device) snprintf((char*)dev->devid, DEVID_MAX_LEN, "hw:%i,%i", dev->cardid, dev->device); - else snprintf((char*)dev->devid, DEVID_MAX_LEN, "hw:%i", dev->cardid); + dev->cardidx = snd_ctl_card_info_get_card(cardInfo); + + // if not provided build a valid PCM cardid + if (!dev->cardid) { + dev->cardid=malloc(ALSA_CARDID_MAX_LEN); + if (dev->subdev) snprintf((char*)dev->cardid, ALSA_CARDID_MAX_LEN, "hw:%i,%i,%i", dev->cardidx, dev->device, dev->subdev); + else if (dev->device) snprintf((char*)dev->cardid, ALSA_CARDID_MAX_LEN, "hw:%i,%i", dev->cardidx, dev->device); + else snprintf((char*)dev->cardid, ALSA_CARDID_MAX_LEN, "hw:%i", dev->cardidx); } + + // make sure UID will cannot be removed + dev->uid= strdup(dev->uid); return 0; OnErrorExit: @@ -89,18 +103,23 @@ OnErrorExit: PUBLIC AlsaPcmInfoT* AlsaByPathOpenPcm(CtlSourceT *source, AlsaPcmInfoT *dev, snd_pcm_stream_t direction) { int error; - - error = AlsaByPathDevid(source, dev); + + // duplicate dev structure to allow caller to free dev + AlsaPcmInfoT* pcm=malloc(sizeof(AlsaPcmInfoT)); + memcpy (pcm, dev, sizeof(AlsaPcmInfoT)); + + + error = AlsaByPathDevid(source, pcm); if (error) goto OnErrorExit; - error = snd_pcm_open(&dev->handle, dev->devid, direction, SND_PCM_NONBLOCK); + error = snd_pcm_open(&pcm->handle, pcm->cardid, direction, SND_PCM_NONBLOCK); if (error) { - AFB_ApiError(source->api, "AlsaByPathOpenPcm: fail openpcm (devid=%s idxdev=%i subdev=%d): %s" - , dev->devid, dev->device, dev->subdev, snd_strerror(error)); + AFB_ApiError(source->api, "AlsaByPathOpenPcm: fail openpcm (cardid=%s idxdev=%i subdev=%d): %s" + , pcm->cardid, pcm->device, pcm->subdev, snd_strerror(error)); goto OnErrorExit; } - return (dev); + return (pcm); OnErrorExit: return NULL; @@ -108,16 +127,16 @@ OnErrorExit: PUBLIC snd_ctl_t *AlsaByPathOpenCtl(CtlSourceT *source, AlsaPcmInfoT *dev) { int err; - char devid[32]; + char cardid[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); + else if (dev->cardid) cardInfo = AlsaCtlGetInfo(source, dev->cardid); if (!cardInfo) { - AFB_ApiError(source->api, "AlsaByPathOpenCtl: fail to find sndcard by path=%s id=%s", dev->devpath, dev->devid); + AFB_ApiError(source->api, "AlsaByPathOpenCtl: fail to find sndcard by path=%s id=%s", dev->devpath, dev->cardid); goto OnErrorExit; } @@ -127,8 +146,8 @@ PUBLIC snd_ctl_t *AlsaByPathOpenCtl(CtlSourceT *source, AlsaPcmInfoT *dev) { 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) { + snprintf(cardid, sizeof (cardid), "hw:%i", cardIndex); + if ((err = snd_ctl_open(&handle, cardid, 0)) < 0) { AFB_ApiError(source->api, "control open (hw:%d -> %s): %s", cardIndex, cardName, snd_strerror(err)); goto OnErrorExit; } diff --git a/plugins/alsa/alsa-utils-dump.c b/plugins/alsa/alsa-utils-dump.c index b3bc864..9ff07d8 100644 --- a/plugins/alsa/alsa-utils-dump.c +++ b/plugins/alsa/alsa-utils-dump.c @@ -21,7 +21,6 @@ #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); @@ -47,8 +46,8 @@ PUBLIC char *AlsaDumpCtlUid(snd_ctl_t *ctlHandle, char *buffer, size_t len) { // 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 *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; @@ -57,7 +56,6 @@ OnErrorExit: return NULL; } - PUBLIC void AlsaDumpFormats(CtlSourceT *source, snd_pcm_t *pcmHandle) { char string[32]; snd_pcm_format_t format; @@ -75,21 +73,20 @@ PUBLIC void AlsaDumpFormats(CtlSourceT *source, snd_pcm_t *pcmHandle) { } } - -PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) { +PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) { snd_ctl_card_info_t *cardInfo; int err; - int dev= -1; + 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) { @@ -108,7 +105,7 @@ PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) { continue; } - AFB_ApiNotice(source->api,"AlsaDumpCard card %d: %s [%s], device %d: %s [%s]", + 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 @@ -128,10 +125,9 @@ PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) { return; OnErrorExit: - return; + return; } - PUBLIC void AlsaDumpPcmParams(CtlSourceT *source, snd_pcm_hw_params_t *pcmHwParams) { snd_output_t *output; char *buffer; @@ -143,8 +139,7 @@ PUBLIC void AlsaDumpPcmParams(CtlSourceT *source, snd_pcm_hw_params_t *pcmHwPara snd_output_close(output); } - -PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, snd_pcm_t *pcm, const char* info) { +PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, const char* info, snd_pcm_t *pcm) { snd_output_t *out; char *buffer; @@ -159,46 +154,59 @@ PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, snd_pcm_t *pcm, const char* info snd_output_close(out); } -PUBLIC void AlsaDumpCtlConfig(CtlSourceT *source, snd_config_t *config, int indent) { +PUBLIC void AlsaDumpElemConfig(CtlSourceT *source, const char* info, const char* elem) { + snd_config_update(); + snd_config_t *pcmConfig; + snd_config_search(snd_config, elem, &pcmConfig); + AlsaDumpCtlConfig(source, info, pcmConfig,1); +} + +PUBLIC void AlsaDumpCtlConfig(CtlSourceT *source, const char* info, snd_config_t *config, int indent) { snd_config_iterator_t it, next; - // hugly hack to get minimalist indentation - char *pretty = alloca(indent + 1); + // 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; - } + 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; + double valueD; + const char *valueS; + + case SND_CONFIG_TYPE_INTEGER: + snd_config_get_integer(node, &valueI); + AFB_ApiNotice(source->api, "%s: %s %s: %d (int)", info, pretty, key, (int) valueI); + break; + + case SND_CONFIG_TYPE_REAL: + snd_config_get_real(node, &valueD); + AFB_ApiNotice(source->api, "%s: %s %s: %.2f (float)", info, pretty, key, valueD); + break; + + case SND_CONFIG_TYPE_STRING: + snd_config_get_string(node, &valueS); + AFB_ApiNotice(source->api, "%s: %s %s: %s (str)", info, pretty, key, valueS); + break; + + case SND_CONFIG_TYPE_COMPOUND: + AFB_ApiNotice(source->api, "%s: %s %s { ", info, pretty, key); + AlsaDumpCtlConfig(source, info, node, indent + 2); + AFB_ApiNotice(source->api, "%s: %s } ", info, pretty); + break; + + default: + snd_config_get_string(node, &valueS); + AFB_ApiNotice(source->api, "%s: %s: key=%s unknown=%s", info, pretty, key, valueS); + break; } + } } |