From 253df14bd84f535a54f4d12c95399899e1343c20 Mon Sep 17 00:00:00 2001 From: Fulup Ar Foll Date: Thu, 17 May 2018 21:43:24 +0200 Subject: Initial version with dynamic APIs --- plugins/alsa/CMakeLists.txt | 2 +- plugins/alsa/alsa-api-backend.c | 218 ++++++++++++++++++++++++ plugins/alsa/alsa-api-frontend.c | 179 +++++++++++++++++++ plugins/alsa/alsa-api-mixer.c | 150 ++++++++++++++++ plugins/alsa/alsa-api-sndcards.c | 210 ----------------------- plugins/alsa/alsa-api-sndloops.c | 162 ------------------ plugins/alsa/alsa-api-sndstreams.c | 235 ------------------------- plugins/alsa/alsa-api-sndzones.c | 144 ---------------- plugins/alsa/alsa-api-streams.c | 340 +++++++++++++++++++++++++++++++++++++ plugins/alsa/alsa-api-zones.c | 156 +++++++++++++++++ plugins/alsa/alsa-core-ctl.c | 1 + plugins/alsa/alsa-plug-multi.c | 8 +- plugins/alsa/alsa-plug-route.c | 11 +- plugins/alsa/alsa-plug-vol.c | 24 +-- plugins/alsa/alsa-softmixer.c | 5 +- plugins/alsa/alsa-softmixer.h | 30 ++-- 16 files changed, 1088 insertions(+), 787 deletions(-) create mode 100644 plugins/alsa/alsa-api-backend.c create mode 100644 plugins/alsa/alsa-api-frontend.c create mode 100644 plugins/alsa/alsa-api-mixer.c delete mode 100644 plugins/alsa/alsa-api-sndcards.c delete mode 100644 plugins/alsa/alsa-api-sndloops.c delete mode 100644 plugins/alsa/alsa-api-sndstreams.c delete mode 100644 plugins/alsa/alsa-api-sndzones.c create mode 100644 plugins/alsa/alsa-api-streams.c create mode 100644 plugins/alsa/alsa-api-zones.c (limited to 'plugins/alsa') diff --git a/plugins/alsa/CMakeLists.txt b/plugins/alsa/CMakeLists.txt index 51f4639..687ccb2 100644 --- a/plugins/alsa/CMakeLists.txt +++ b/plugins/alsa/CMakeLists.txt @@ -17,7 +17,7 @@ ########################################################################### -PROJECT_TARGET_ADD(alsa-router) +PROJECT_TARGET_ADD(alsa-softmixer) file(GLOB SOURCE_FILES "alsa-*.c") diff --git a/plugins/alsa/alsa-api-backend.c b/plugins/alsa/alsa-api-backend.c new file mode 100644 index 0000000..948d30d --- /dev/null +++ b/plugins/alsa/alsa-api-backend.c @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author Fulup Ar Foll + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#define _GNU_SOURCE // needed for vasprintf + +#include "alsa-softmixer.h" + +// 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; +} + +PUBLIC int ProcessSndParams(CtlSourceT *source, const char* uid, json_object *paramsJ, AlsaPcmHwInfoT *params) { + const char *format = NULL, *access = NULL; + + // some default values + params->rate = ALSA_DEFAULT_PCM_RATE; + params->channels = 2; + params->sampleSize = 0; + + int error = wrap_json_unpack(paramsJ, "{s?i,s?i, s?s, s?s !}", "rate", ¶ms->rate, "channels", ¶ms->channels, "format", &format, "access", &access); + if (error) goto OnErrorExit; + + if (!format) params->format = SND_PCM_FORMAT_S16_LE; + else if (!strcasecmp(format, "S16_LE")) params->format = SND_PCM_FORMAT_S16_LE; + else if (!strcasecmp(format, "S16_BE")) params->format = SND_PCM_FORMAT_S16_BE; + else if (!strcasecmp(format, "U16_LE")) params->format = SND_PCM_FORMAT_U16_LE; + else if (!strcasecmp(format, "U16_BE")) params->format = SND_PCM_FORMAT_U16_BE; + else if (!strcasecmp(format, "S32_LE")) params->format = SND_PCM_FORMAT_S32_LE; + else if (!strcasecmp(format, "S32_BE")) params->format = SND_PCM_FORMAT_S32_BE; + else if (!strcasecmp(format, "U32_LE")) params->format = SND_PCM_FORMAT_U32_LE; + else if (!strcasecmp(format, "U32_BE")) params->format = SND_PCM_FORMAT_U32_BE; + else if (!strcasecmp(format, "S24_LE")) params->format = SND_PCM_FORMAT_S24_LE; + else if (!strcasecmp(format, "S24_BE")) params->format = SND_PCM_FORMAT_S24_BE; + else if (!strcasecmp(format, "U24_LE")) params->format = SND_PCM_FORMAT_U24_LE; + else if (!strcasecmp(format, "U24_BE")) params->format = SND_PCM_FORMAT_U24_BE; + else if (!strcasecmp(format, "S8")) params->format = SND_PCM_FORMAT_S8; + else if (!strcasecmp(format, "U8")) params->format = SND_PCM_FORMAT_U8; + else if (!strcasecmp(format, "FLOAT_LE")) params->format = SND_PCM_FORMAT_FLOAT_LE; + else if (!strcasecmp(format, "FLOAT_BE")) params->format = SND_PCM_FORMAT_FLOAT_LE; + else { + AFB_ApiNotice(source->api, "ProcessSndParams:%s(params) unsupported format 'S16_LE|S32_L|...' format=%s", uid, format); + goto OnErrorExit; + } + + if (!access) params->access = SND_PCM_ACCESS_RW_INTERLEAVED; + else if (!strcasecmp(access, "MMAP_INTERLEAVED")) params->access = SND_PCM_ACCESS_MMAP_INTERLEAVED; + else if (!strcasecmp(access, "MMAP_NONINTERLEAVED")) params->access = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; + else if (!strcasecmp(access, "MMAP_COMPLEX")) params->access = SND_PCM_ACCESS_MMAP_COMPLEX; + else if (!strcasecmp(access, "RW_INTERLEAVED")) params->access = SND_PCM_ACCESS_RW_INTERLEAVED; + else if (!strcasecmp(access, "RW_NONINTERLEAVED")) params->access = SND_PCM_ACCESS_RW_NONINTERLEAVED; + + else { + AFB_ApiNotice(source->api, "ProcessSndParams:%s(params) unsupported access 'RW_INTERLEAVED|MMAP_INTERLEAVED|MMAP_COMPLEX' access=%s", uid, access); + goto OnErrorExit; + } + + return 0; + +OnErrorExit: + return -1; +} + +STATIC int ProcessOneSndCard(CtlSourceT *source, json_object *sndcardJ, AlsaPcmInfoT *snd) { + json_object *sinkJ = NULL, *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; + } + } else { + snd->params.rate = ALSA_DEFAULT_PCM_RATE; + snd->params.access = SND_PCM_ACCESS_RW_INTERLEAVED; + snd->params.format = SND_PCM_FORMAT_S16_LE; + snd->params.channels = 2; + } + + // 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 + char dmixUid[100]; + snprintf(dmixUid, sizeof (dmixUid), "Dmix-%s", snd->uid); + AlsaPcmInfoT *dmixPcm = AlsaCreateDmix(source, dmixUid, snd, 0); + if (!dmixPcm) { + AFB_ApiError(source->api, "ProcessOneSndCard: sndcard=%s fail to attach dmix plugin", snd->uid); + goto OnErrorExit; + } else { + 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; +} + +PUBLIC int SndBackend(CtlSourceT *source, json_object *argsJ) { + SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; + int error; + size_t count; + + assert(mixerHandle); + + if (mixerHandle->backend) { + AFB_ApiError(source->api, "SndBackend: mixer=%s backend already declared %s", mixerHandle->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + switch (json_object_get_type(argsJ)) { + case json_type_object: + count = 1; + mixerHandle->backend = calloc(count + 1, sizeof (AlsaPcmInfoT)); + error = ProcessOneSndCard(source, argsJ, &mixerHandle->backend[0]); + if (error) goto OnErrorExit; + break; + case json_type_array: + count = json_object_array_length(argsJ); + mixerHandle->backend = 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, &mixerHandle->backend[idx]); + if (error) goto OnErrorExit; + } + break; + default: + AFB_ApiError(source->api, "SndBackend: mixer=%s invalid argsJ= %s", mixerHandle->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + + if (count == 1) { + // only one sound card we multi would be useless + mixerHandle->multiPcm = &mixerHandle->backend[0]; + + } else { + + // instantiate an alsa multi plugin + mixerHandle->multiPcm = AlsaCreateMulti(source, "PcmMulti", 0); + if (!mixerHandle->multiPcm) goto OnErrorExit; + + } + return 0; + +OnErrorExit: + AFB_ApiNotice(source->api, "SndBackend mixer=%s fail to process: %s", mixerHandle->uid, json_object_get_string(argsJ)); + return -1; +} diff --git a/plugins/alsa/alsa-api-frontend.c b/plugins/alsa/alsa-api-frontend.c new file mode 100644 index 0000000..c01a88b --- /dev/null +++ b/plugins/alsa/alsa-api-frontend.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author Fulup Ar Foll + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#define _GNU_SOURCE // needed for vasprintf + +#include "alsa-softmixer.h" +#include + +// Fulup need to be cleanup with new controller version +extern Lua2cWrapperT Lua2cWrap; + +STATIC int ProcessOneSubdev(CtlSourceT *source, AlsaSndLoopT *loop, json_object *subdevJ, AlsaPcmHwInfoT *loopDefParams,AlsaPcmInfoT *subdev) { + json_object *paramsJ = NULL; + + int error = wrap_json_unpack(subdevJ, "{si,si,s?o !}", "subdev", &subdev->subdev, "numid", &subdev->numid, "params", ¶msJ); + if (error) { + AFB_ApiError(source->api, "ProcessOneSubdev: loop=%s missing (uid|subdev|numid) json=%s", loop->uid, json_object_get_string(subdevJ)); + goto OnErrorExit; + } + + if (paramsJ) { + error = ProcessSndParams(source, loop->uid, paramsJ, loopDefParams); + if (error) { + AFB_ApiError(source->api, "ProcessOneLoop: sndcard=%s invalid params=%s", loop->uid, json_object_get_string(paramsJ)); + goto OnErrorExit; + } + } else { + // use global loop params definition as default + memcpy (&subdev->params, loopDefParams, sizeof(AlsaPcmHwInfoT)); + } + // 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, *paramsJ = NULL; + int error; + + error = wrap_json_unpack(loopJ, "{ss,s?s,s?s,s?i,s?o,so,s?o !}", "uid", &loop->uid, "devpath", &loop->devpath, "cardid", &loop->cardid + , "cardidx", &loop->cardidx, "devices", &devicesJ, "subdevs", &subdevsJ, "params", ¶msJ); + 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; + } + + AlsaPcmHwInfoT *loopDefParams =alloca(sizeof(AlsaPcmHwInfoT)); + if (paramsJ) { + error = ProcessSndParams(source, loop->uid, paramsJ, loopDefParams); + if (error) { + AFB_ApiError(source->api, "ProcessOneLoop: sndcard=%s invalid params=%s", loop->uid, json_object_get_string(paramsJ)); + goto OnErrorExit; + } + } else { + loopDefParams->rate = ALSA_DEFAULT_PCM_RATE; + loopDefParams->rate= ALSA_DEFAULT_PCM_RATE; + loopDefParams->access=SND_PCM_ACCESS_RW_INTERLEAVED; + loopDefParams->format=SND_PCM_FORMAT_S16_LE; + loopDefParams->channels=2; + loopDefParams->sampleSize=0; + } + + // 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, loopDefParams, &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, loopDefParams, &loop->subdevs[idx]); + if (error) goto OnErrorExit; + } + break; + default: + AFB_ApiError(source->api, "ProcessOneLoop=%s invalid subdevs= %s", loop->uid, json_object_get_string(subdevsJ)); + goto OnErrorExit; + } + + return 0; + +OnErrorExit: + return -1; +} + +PUBLIC int SndFrontend (CtlSourceT *source, json_object *argsJ) { + SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; + int error; + + assert (mixerHandle); + + if (mixerHandle->loop) { + AFB_ApiError(source->api, "SndFrontend: mixer=%s SndFrontend already declared %s", mixerHandle->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + mixerHandle->loop= calloc(1, sizeof (AlsaSndLoopT)); + + // or syntax purpose array is accepted but frontend should have a single driver entry + json_type type= json_object_get_type(argsJ); + if (type == json_type_array) { + size_t count= json_object_array_length(argsJ); + if (count != 1) { + AFB_ApiError(source->api, "SndFrontend: mixer=%s frontend only support on input driver args=%s", mixerHandle->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + argsJ= json_object_array_get_idx(argsJ,0); + } + + type= json_object_get_type(argsJ); + if (type != json_type_object) { + AFB_ApiError(source->api, "SndFrontend: mixer=%s invalid object type= %s", mixerHandle->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + error = ProcessOneLoop(source, argsJ, mixerHandle->loop); + if (error) { + AFB_ApiError(source->api, "SndFrontend: mixer=%s invalid object= %s", mixerHandle->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + return 0; + +OnErrorExit: + return -1; +} \ No newline at end of file diff --git a/plugins/alsa/alsa-api-mixer.c b/plugins/alsa/alsa-api-mixer.c new file mode 100644 index 0000000..f1619c2 --- /dev/null +++ b/plugins/alsa/alsa-api-mixer.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author Fulup Ar Foll + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#define _GNU_SOURCE // needed for vasprintf + +#include "alsa-softmixer.h" +#include + +// Fulup need to be cleanup with new controller version +extern Lua2cWrapperT Lua2cWrap; + +// API + +static void MixerApiVerbCB(AFB_ReqT request) { + json_object *responseJ, *backendJ = NULL, *frontendJ = NULL, *zonesJ = NULL, *streamsJ = NULL; + // retrieve action handle from request and execute the request + json_object *argsJ = afb_request_json(request); + SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) afb_request_get_vcbdata(request); + int error; + int close = 0; + + CtlSourceT *source = alloca(sizeof (CtlSourceT)); + source->uid = mixerHandle->uid; + source->api = request->dynapi; + source->request = request; + source->context = mixerHandle; + + error = wrap_json_unpack(argsJ, "{s?b s?o,s?o,s?o,s?o !}" + , "close", &close + , "backend", &backendJ + , "frontend", &frontendJ + , "zones", &zonesJ + , "streams", &streamsJ + ); + if (error) { + AFB_ReqFailF(request, "MixerApiVerbCB", "missing 'uid|backend|frontend|zones|streams' mixer=%s", json_object_get_string(argsJ)); + goto OnErrorExit; + } + + // Free attached resources and free mixer + if (close) { + AFB_ReqFailF(request, "MixerApiVerbCB", "(Fulup) Close action still to be done mixer=%s", json_object_get_string(argsJ)); + goto OnErrorExit; + } + + if (backendJ) { + error = SndBackend(source, backendJ); + if (error) goto OnErrorExit; + } + + if (frontendJ) { + error = SndFrontend(source, frontendJ); + if (error) goto OnErrorExit; + } + + if (zonesJ) { + error = SndZones(source, zonesJ); + if (error) goto OnErrorExit; + } + + if (streamsJ) { + error = SndStreams(source, streamsJ, &responseJ); + if (error) goto OnErrorExit; + } + + + AFB_ReqSucess(request, responseJ, mixerHandle->uid); + return; + +OnErrorExit: + return; +} + +CTLP_LUA2C(_mixer_new_, source, argsJ, responseJ) { + SoftMixerHandleT *mixerHandle = calloc(1, sizeof (SoftMixerHandleT)); + json_object *backendJ = NULL, *frontendJ = NULL, *zonesJ = NULL, *streamsJ = NULL; + int error; + assert(source->api); + + if (json_object_get_type(argsJ) != json_type_object) { + AFB_ApiError(source->api, "_mixer_new_: invalid object type= %s", json_object_get_string(argsJ)); + goto OnErrorExit; + } + + error = wrap_json_unpack(argsJ, "{ss,s?s,s?o,s?o,s?o,s?o !}" + , "uid", &mixerHandle->uid + , "info", &mixerHandle->info + , "backend", &backendJ + , "frontend", &frontendJ + , "zones", &zonesJ + , "streams", &streamsJ); + if (error) { + AFB_ApiNotice(source->api, "_mixer_new_ missing 'uid|backend|frontend|zones|streams' mixer=%s", json_object_get_string(argsJ)); + goto OnErrorExit; + } + + // make sure string do not get deleted + mixerHandle->uid = strdup(mixerHandle->uid); + if (mixerHandle->info)mixerHandle->info = strdup(mixerHandle->info); + + // create mixer verb within API. + error = afb_dynapi_add_verb(source->api, mixerHandle->uid, mixerHandle->info, MixerApiVerbCB, mixerHandle, NULL, 0); + if (error) { + AFB_ApiError(source->api, "_mixer_new_ mixer=%s fail to register API verb", mixerHandle->uid); + return -1; + } + + // make sure sub command get access to mixer handle + source->context = mixerHandle; + + if (backendJ) { + error = SndBackend(source, backendJ); + if (error) goto OnErrorExit; + } + + if (frontendJ) { + error = SndFrontend(source, frontendJ); + if (error) goto OnErrorExit; + } + + if (zonesJ) { + error = SndZones(source, zonesJ); + if (error) goto OnErrorExit; + } + + if (streamsJ) { + error = SndStreams(source, streamsJ, responseJ); + if (error) goto OnErrorExit; + } + + return 0; + +OnErrorExit: + return -1; +} \ No newline at end of file diff --git a/plugins/alsa/alsa-api-sndcards.c b/plugins/alsa/alsa-api-sndcards.c deleted file mode 100644 index daa996a..0000000 --- a/plugins/alsa/alsa-api-sndcards.c +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2018 "IoT.bzh" - * Author Fulup Ar Foll - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#define _GNU_SOURCE // needed for vasprintf - -#include "alsa-softmixer.h" - -// 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; -} - -PUBLIC int ProcessSndParams(CtlSourceT *source, const char* uid, json_object *paramsJ, AlsaPcmHwInfoT *params) { - const char *format=NULL, *access=NULL; - - // some default values - params->rate= ALSA_DEFAULT_PCM_RATE; - params->channels= 2; - params->sampleSize=0; - - int error = wrap_json_unpack(paramsJ, "{s?i,s?i, s?s, s?s !}", "rate",¶ms->rate,"channels", ¶ms->channels, "format",&format, "access",&access); - if (error) goto OnErrorExit; - - if (!format) params->format = SND_PCM_FORMAT_S16_LE; - else if (!strcasecmp(format, "S16_LE")) params->format = SND_PCM_FORMAT_S16_LE; - else if (!strcasecmp(format, "S16_BE")) params->format = SND_PCM_FORMAT_S16_BE; - else if (!strcasecmp(format, "U16_LE")) params->format = SND_PCM_FORMAT_U16_LE; - else if (!strcasecmp(format, "U16_BE")) params->format = SND_PCM_FORMAT_U16_BE; - else if (!strcasecmp(format, "S32_LE")) params->format = SND_PCM_FORMAT_S32_LE; - else if (!strcasecmp(format, "S32_BE")) params->format = SND_PCM_FORMAT_S32_BE; - else if (!strcasecmp(format, "U32_LE")) params->format = SND_PCM_FORMAT_U32_LE; - else if (!strcasecmp(format, "U32_BE")) params->format = SND_PCM_FORMAT_U32_BE; - else if (!strcasecmp(format, "S24_LE")) params->format = SND_PCM_FORMAT_S24_LE; - else if (!strcasecmp(format, "S24_BE")) params->format = SND_PCM_FORMAT_S24_BE; - else if (!strcasecmp(format, "U24_LE")) params->format = SND_PCM_FORMAT_U24_LE; - else if (!strcasecmp(format, "U24_BE")) params->format = SND_PCM_FORMAT_U24_BE; - else if (!strcasecmp(format, "S8")) params->format = SND_PCM_FORMAT_S8; - else if (!strcasecmp(format, "U8")) params->format = SND_PCM_FORMAT_U8; - else if (!strcasecmp(format, "FLOAT_LE")) params->format = SND_PCM_FORMAT_FLOAT_LE; - else if (!strcasecmp(format, "FLOAT_BE")) params->format = SND_PCM_FORMAT_FLOAT_LE; - else { - AFB_ApiNotice(source->api, "ProcessSndParams:%s(params) unsupported format 'S16_LE|S32_L|...' format=%s", uid, format); - goto OnErrorExit; - } - - if (!access) params->access = SND_PCM_ACCESS_RW_INTERLEAVED; - else if (!strcasecmp(access, "MMAP_INTERLEAVED")) params->access = SND_PCM_ACCESS_MMAP_INTERLEAVED; - else if (!strcasecmp(access, "MMAP_NONINTERLEAVED")) params->access = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; - else if (!strcasecmp(access, "MMAP_COMPLEX")) params->access = SND_PCM_ACCESS_MMAP_COMPLEX; - else if (!strcasecmp(access, "RW_INTERLEAVED")) params->access = SND_PCM_ACCESS_RW_INTERLEAVED; - else if (!strcasecmp(access, "RW_NONINTERLEAVED")) params->access = SND_PCM_ACCESS_RW_NONINTERLEAVED; - - else { - AFB_ApiNotice(source->api, "ProcessSndParams:%s(params) unsupported access 'RW_INTERLEAVED|MMAP_INTERLEAVED|MMAP_COMPLEX' access=%s",uid, access); - goto OnErrorExit; - } - - return 0; - -OnErrorExit: - return -1; -} - -STATIC int ProcessOneSndCard(CtlSourceT *source, json_object *sndcardJ, AlsaPcmInfoT *snd) { - json_object *sinkJ=NULL, *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; - } - } else { - snd->params.rate= ALSA_DEFAULT_PCM_RATE; - snd->params.access= SND_PCM_ACCESS_RW_INTERLEAVED; - snd->params.format=SND_PCM_FORMAT_S16_LE; - snd->params.channels=2; - } - - // 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 - char dmixUid[100]; - snprintf(dmixUid, sizeof (dmixUid), "Dmix-%s", snd->uid); - AlsaPcmInfoT *dmixPcm = AlsaCreateDmix(source, dmixUid, snd, 0); - if (!dmixPcm) { - AFB_ApiError(source->api, "ProcessOneSndCard: sndcard=%s fail to attach dmix plugin", snd->uid); - goto OnErrorExit; - } else { - 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", 0); - if (!pcmMulti) goto OnErrorExit; - - 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 deleted file mode 100644 index cf7bf55..0000000 --- a/plugins/alsa/alsa-api-sndloops.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2018 "IoT.bzh" - * Author Fulup Ar Foll - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#define _GNU_SOURCE // needed for vasprintf - -#include "alsa-softmixer.h" -#include - -// Fulup need to be cleanup with new controller version -extern Lua2cWrapperT Lua2cWrap; - -STATIC int ProcessOneSubdev(CtlSourceT *source, AlsaSndLoopT *loop, json_object *subdevJ, AlsaPcmHwInfoT *loopDefParams,AlsaPcmInfoT *subdev) { - json_object *paramsJ = NULL; - - int error = wrap_json_unpack(subdevJ, "{si,si,s?o !}", "subdev", &subdev->subdev, "numid", &subdev->numid, "params", ¶msJ); - if (error) { - AFB_ApiError(source->api, "ProcessOneSubdev: loop=%s missing (uid|subdev|numid) json=%s", loop->uid, json_object_get_string(subdevJ)); - goto OnErrorExit; - } - - if (paramsJ) { - error = ProcessSndParams(source, loop->uid, paramsJ, loopDefParams); - if (error) { - AFB_ApiError(source->api, "ProcessOneLoop: sndcard=%s invalid params=%s", loop->uid, json_object_get_string(paramsJ)); - goto OnErrorExit; - } - } else { - // use global loop params definition as default - memcpy (&subdev->params, loopDefParams, sizeof(AlsaPcmHwInfoT)); - } - // 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, *paramsJ = NULL; - int error; - - error = wrap_json_unpack(loopJ, "{ss,s?s,s?s,s?i,s?o,so,s?o !}", "uid", &loop->uid, "devpath", &loop->devpath, "cardid", &loop->cardid - , "cardidx", &loop->cardidx, "devices", &devicesJ, "subdevs", &subdevsJ, "params", ¶msJ); - 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; - } - - AlsaPcmHwInfoT *loopDefParams =alloca(sizeof(AlsaPcmHwInfoT)); - if (paramsJ) { - error = ProcessSndParams(source, loop->uid, paramsJ, loopDefParams); - if (error) { - AFB_ApiError(source->api, "ProcessOneLoop: sndcard=%s invalid params=%s", loop->uid, json_object_get_string(paramsJ)); - goto OnErrorExit; - } - } else { - loopDefParams->rate = ALSA_DEFAULT_PCM_RATE; - loopDefParams->rate= ALSA_DEFAULT_PCM_RATE; - loopDefParams->access=SND_PCM_ACCESS_RW_INTERLEAVED; - loopDefParams->format=SND_PCM_FORMAT_S16_LE; - loopDefParams->channels=2; - loopDefParams->sampleSize=0; - } - - // 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, loopDefParams, &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, loopDefParams, &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 deleted file mode 100644 index e55aeff..0000000 --- a/plugins/alsa/alsa-api-sndstreams.c +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (C) 2018 "IoT.bzh" - * Author Fulup Ar Foll - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#define _GNU_SOURCE // needed for vasprintf - -#include "alsa-softmixer.h" -#include - -// Set stream volume control in % -#define VOL_CONTROL_MAX 100 -#define VOL_CONTROL_MIN 0 -#define VOL_CONTROL_STEP 1 - -// Fulup need to be cleanup with new controller version -extern Lua2cWrapperT Lua2cWrap; - -STATIC int ProcessOneStream(CtlSourceT *source, json_object *streamJ, AlsaSndStreamT *stream) { - int error; - json_object *paramsJ = NULL; - - // Make sure default runs - stream->volume = ALSA_DEFAULT_PCM_VOLUME; - stream->mute = 0; - - error = wrap_json_unpack(streamJ, "{ss,ss,s?i,s?b,s?o !}", "uid", &stream->uid, "zone", &stream->zone, "volume", &stream->volume - , "mute", stream->mute, "params", ¶msJ); - if (error) { - AFB_ApiNotice(source->api, "ProcessOneStream missing 'uid|zone|volume|rate|mute|params' stream=%s", json_object_get_string(streamJ)); - goto OnErrorExit; - } - - if (paramsJ) error = ProcessSndParams(source, stream->uid, paramsJ, &stream->params); - if (error) { - AFB_ApiError(source->api, "ProcessOneSndCard: sndcard=%s invalid params=%s", stream->uid, json_object_get_string(paramsJ)); - goto OnErrorExit; - } else { - stream->params.rate = ALSA_DEFAULT_PCM_RATE; - stream->params.rate = ALSA_DEFAULT_PCM_RATE; - stream->params.access = SND_PCM_ACCESS_RW_INTERLEAVED; - stream->params.format = SND_PCM_FORMAT_S16_LE; - stream->params.channels = 2; - stream->params.sampleSize = 0; - } - - // 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; - long value; - 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; - } - - - // return stream data to application as a json array - *responseJ = json_object_new_array(); - - for (int idx = 0; sndStream[idx].uid != NULL; idx++) { - json_object *streamJ, *paramsJ; - - // 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 *playbackDev = &ctlLoop->subdevs[ctlLoop->scount]; - - // capture use the same card/subdev as playback with a different device - playbackDev->device = ctlLoop->capture; - AlsaPcmInfoT *captureDev = AlsaByPathOpenPcm(source, playbackDev, SND_PCM_STREAM_CAPTURE); - if (!captureDev) goto OnErrorExit; - - // configure with default loopback subdev params - error = AlsaPcmConf(source, captureDev, &playbackDev->params); - if (error) goto OnErrorExit; - - // Register capture PCM for active/pause event - if (captureDev->numid) { - error = AlsaCtlRegister(source, captureDev, captureDev->numid); - if (error) goto OnErrorExit; - } - - // Try to create/setup volume control. - snd_ctl_t* ctlDev = AlsaCrlFromPcm(source, captureDev->handle); - if (!ctlDev) { - AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail attache sndcard", captureDev->cardid); - goto OnErrorExit; - } - - // create mute control and register it as pause/resume ctl) - char runName[ALSA_CARDID_MAX_LEN]; - snprintf(runName, sizeof (runName), "run-%s", sndStream[idx].uid); - - // create a single boolean value control for pause/resume - int runNumid = AlsaCtlCreateControl(source, ctlDev, playbackDev, runName, 1, 0, 1, 1, !sndStream[idx].mute); - if (runNumid <= 0) goto OnErrorExit; - - // register mute/unmute as a pause/resume control - error = AlsaCtlRegister(source, captureDev, runNumid); - if (error) goto OnErrorExit; - - // create stream and delay pcm openning until vol control is created - char volName[ALSA_CARDID_MAX_LEN]; - snprintf(volName, sizeof (volName), "vol-%s", sndStream[idx].uid); - AlsaPcmInfoT *streamPcm = AlsaCreateStream(source, &sndStream[idx], captureDev, volName, VOL_CONTROL_MAX, 0); - if (!streamPcm) { - AFB_ApiError(source->api, "L2C:sndstreams:%s(pcm) fail to create stream", sndStream[idx].uid); - goto OnErrorExit; - } - - // create volume control before softvol pcm is opened - int volNumid = AlsaCtlCreateControl(source, ctlDev, playbackDev, volName, streamPcm->params.channels, VOL_CONTROL_MIN, VOL_CONTROL_MAX, VOL_CONTROL_STEP, sndStream[idx].volume); - if (volNumid <= 0) goto OnErrorExit; - - // **** Fulup (would need some help to get automatic rate converter to work). - // // add a rate converter plugin to match stream params config - // char rateName[ALSA_CARDID_MAX_LEN]; - // snprintf(rateName, sizeof (rateName), "rate-%s", sndStream[idx].uid); - // AlsaPcmInfoT *ratePcm= AlsaCreateRate(source, rateName, streamPcm, 1); - // if (!ratePcm) { - // AFB_ApiError(source->api, "L2C:sndstreams:%s(pcm) fail to create rate converter", sndStream[idx].uid); - // goto OnErrorExit; - // } - - // everything is not ready to open capture pcm - error = snd_pcm_open(&streamPcm->handle, sndStream[idx].uid, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); - if (error) { - AFB_ApiError(source->api, "L2C:sndstreams:%s(pcm) fail to open capture", sndStream[idx].uid); - goto OnErrorExit; - } - - // capture stream inherit channel from targeted zone - captureDev->ccount = streamPcm->ccount; - streamPcm->params.channels = streamPcm->ccount; - - // start stream pcm copy (at this both capture & sink pcm should be open) - error = AlsaPcmCopy(source, captureDev, streamPcm, &streamPcm->params); - if (error) goto OnErrorExit; - - // retrieve active/pause control and set PCM status accordingly - error = AlsaCtlNumidGetLong(source, ctlDev, captureDev->numid, &value); - if (error) goto OnErrorExit; - - // toggle pause/resume (should be done after pcm_start) - if ((error = snd_pcm_pause(captureDev->handle, !value)) < 0) { - AFB_ApiError(source->api, "L2C:sndstreams [capture=%s] fail to pause error=%s", captureDev->cardid, snd_strerror(error)); - goto OnErrorExit; - } - - // prepare response for application - playbackDev->device = ctlLoop->playback; - error = AlsaByPathDevid(source, playbackDev); - - error += wrap_json_pack(¶msJ, "{si si si si}", "rate", streamPcm->params.rate, "channels", streamPcm->params.channels, "format", streamPcm->params.format, "access", streamPcm->params.access); - error += wrap_json_pack(&streamJ, "{ss ss si si so}", "uid", streamPcm->uid, "alsa", playbackDev->cardid, "volid", volNumid, "runid", runNumid, "params", paramsJ); - error += json_object_array_add(*responseJ, streamJ); - if (error) { - AFB_ApiError(source->api, "L2C:sndstreams:%s(stream) fail to prepare response", captureDev->cardid); - goto OnErrorExit; - } - - snd_ctl_close(ctlDev); - // 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:%s(stream) OK reponse=%s\n", streamPcm->uid, json_object_get_string(streamJ)); - } - - 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 deleted file mode 100644 index 7ca61ad..0000000 --- a/plugins/alsa/alsa-api-sndzones.c +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2018 "IoT.bzh" - * Author Fulup Ar Foll - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#define _GNU_SOURCE // needed for vasprintf - -#include "alsa-softmixer.h" -#include - -// 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], 0); - if (!Softmixer->zonePcms[idx]) { - AFB_ApiNotice(source->api, "L2C:sndzones fail to create route zone=%s", sndZone[idx].uid); - goto OnErrorExit; - } - } - - // 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-api-streams.c b/plugins/alsa/alsa-api-streams.c new file mode 100644 index 0000000..42d8ade --- /dev/null +++ b/plugins/alsa/alsa-api-streams.c @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author Fulup Ar Foll + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#define _GNU_SOURCE // needed for vasprintf + +#include "alsa-softmixer.h" +#include + +// Set stream volume control in % +#define VOL_CONTROL_MAX 100 +#define VOL_CONTROL_MIN 0 +#define VOL_CONTROL_STEP 1 + +// Fulup need to be cleanup with new controller version +extern Lua2cWrapperT Lua2cWrap; + +typedef struct { + const char* verb; + AlsaSndStreamT *streams; + SoftMixerHandleT *mixer; +} apiHandleT; + +static void StreamApiVerbCB(AFB_ReqT request) { + int close=0, error=0, quiet=0; + long volume=-1, mute=-1; + json_object *responseJ, *argsJ= afb_request_json(request); + apiHandleT *handle = (apiHandleT*) afb_request_get_vcbdata(request); + + CtlSourceT *source = alloca(sizeof (CtlSourceT)); + source->uid = handle->verb; + source->api = request->dynapi; + source->request = NULL; + source->context = NULL; + + error = wrap_json_unpack(argsJ, "{s?b s?b,s?b, s?i !}" + , "quiet", &quiet + , "close", &close + , "mute", &mute + , "volume", &volume + ); + if (error) { + AFB_ReqFailF(request, "StreamApiVerbCB", "Missing 'close|mute|volume|quiet' args=%s", json_object_get_string(argsJ)); + goto OnErrorExit; + } + + snd_ctl_t *ctlDev= AlsaCtlOpenCtl (source, handle->mixer->loop->cardid); + if (!ctlDev) { + AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to open sndcard=%s", handle->mixer->loop->cardid); + goto OnErrorExit; + } + + if (close) { + AFB_ReqFailF(request, "StreamApiVerbCB", "(Fulup) Close action still to be done mixer=%s", json_object_get_string(argsJ)); + goto OnErrorExit; + } + + if (volume != -1) { + error= AlsaCtlNumidSetLong (source, ctlDev, handle->streams->volume, volume); + if (error) { + AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volume numid=%d value=%ld", handle->streams->volume, volume); + goto OnErrorExit; + } + } + + if (mute != -1) { + error= AlsaCtlNumidSetLong (source, ctlDev, handle->streams->volume, !mute); + if (error) { + AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volume numid=%d value=%d", handle->streams->volume, !mute); + goto OnErrorExit; + } + } + + // if not in quiet mode return effective selected control values + if (quiet) responseJ=NULL; + else { + error+= AlsaCtlNumidGetLong (source, ctlDev, handle->streams->volume, &volume); + error+= AlsaCtlNumidGetLong (source, ctlDev, handle->streams->mute, &mute); + if (error) { + AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to get stream numids volume=%ld mute=%ld",volume, mute); + goto OnErrorExit; + } + wrap_json_pack (&responseJ,"{volume:si, mute:sb}", volume, mute); + } + + AFB_ReqSucess(request, responseJ, handle->verb); + return; + +OnErrorExit: + return; + +} + +STATIC int ProcessOneStream(CtlSourceT *source, json_object *streamJ, AlsaSndStreamT *stream) { + int error; + json_object *paramsJ = NULL; + + // Make sure default runs + stream->volume = ALSA_DEFAULT_PCM_VOLUME; + stream->mute = 0; + stream->info = NULL; + + error = wrap_json_unpack(streamJ, "{ss,s?s,ss,s?i,s?b,s?o !}" + , "uid", &stream->uid + , "info", &stream->info + , "zone", &stream->zone + , "volume", &stream->volume + , "mute", stream->mute + , "params", ¶msJ); + if (error) { + AFB_ApiNotice(source->api, "ProcessOneStream missing 'uid|[info]|zone|[volume]|[mute]|[params]' stream=%s", json_object_get_string(streamJ)); + goto OnErrorExit; + } + + if (paramsJ) error = ProcessSndParams(source, stream->uid, paramsJ, &stream->params); + if (error) { + AFB_ApiError(source->api, "ProcessOneSndCard: sndcard=%s invalid params=%s", stream->uid, json_object_get_string(paramsJ)); + goto OnErrorExit; + } else { + stream->params.rate = ALSA_DEFAULT_PCM_RATE; + stream->params.rate = ALSA_DEFAULT_PCM_RATE; + stream->params.access = SND_PCM_ACCESS_RW_INTERLEAVED; + stream->params.format = SND_PCM_FORMAT_S16_LE; + stream->params.channels = 2; + stream->params.sampleSize = 0; + } + + // 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; +} + +PUBLIC int SndStreams(CtlSourceT *source, json_object *argsJ, json_object **responseJ) { + SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; + AlsaSndStreamT *sndStream; + int error; + long value; + size_t count; + + assert(mixerHandle); + + // assert static/global softmixer handle get requited info + AlsaSndLoopT *ctlLoop = mixerHandle->loop; + if (!ctlLoop) { + AFB_ApiError(source->api, "SndStreams: mixer=%s No Loop found [should register snd_loop first]", mixerHandle->uid); + 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, "SndStreams: mixer=%s invalid stream= %s", mixerHandle->uid, 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: mixer=%s invalid stream= %s", mixerHandle->uid, json_object_get_string(sndStreamJ)); + goto OnErrorExit; + } + } + break; + default: + AFB_ApiError(source->api, "SndStreams: mixer=%s invalid argsJ= %s", mixerHandle->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + + // return stream data to application as a json array + *responseJ = json_object_new_array(); + + for (int idx = 0; sndStream[idx].uid != NULL; idx++) { + json_object *streamJ, *paramsJ; + + // Search for a free loop capture device + AFB_ApiNotice(source->api, "SndStreams: mixer=%s stream=%s Start", mixerHandle->uid, (char*) sndStream[idx].uid); + ctlLoop->scount--; + if (ctlLoop->scount < 0) { + AFB_ApiError(source->api, "SndStreams: mixer=%s stream=%s no more subdev avaliable in loopback=%s", mixerHandle->uid, sndStream[idx].uid, ctlLoop->uid); + goto OnErrorExit; + } + + // Retrieve subdev loop device and open corresponding pcm + AlsaPcmInfoT *playbackDev = &ctlLoop->subdevs[ctlLoop->scount]; + + // capture use the same card/subdev as playback with a different device + playbackDev->device = ctlLoop->capture; + AlsaPcmInfoT *captureDev = AlsaByPathOpenPcm(source, playbackDev, SND_PCM_STREAM_CAPTURE); + if (!captureDev) goto OnErrorExit; + + // configure with default loopback subdev params + error = AlsaPcmConf(source, captureDev, &playbackDev->params); + if (error) goto OnErrorExit; + + // Register capture PCM for active/pause event + if (captureDev->numid) { + error = AlsaCtlRegister(source, captureDev, captureDev->numid); + if (error) goto OnErrorExit; + } + + // Try to create/setup volume control. + snd_ctl_t* ctlDev = AlsaCrlFromPcm(source, captureDev->handle); + if (!ctlDev) { + AFB_ApiError(source->api, "SndStreams: mixer=%s [pcm=%s] fail attache sndcard", mixerHandle->uid, captureDev->cardid); + goto OnErrorExit; + } + + // create mute control and register it as pause/resume ctl) + char runName[ALSA_CARDID_MAX_LEN]; + snprintf(runName, sizeof (runName), "run-%s", sndStream[idx].uid); + + // create a single boolean value control for pause/resume + int runNumid = AlsaCtlCreateControl(source, ctlDev, playbackDev, runName, 1, 0, 1, 1, !sndStream[idx].mute); + if (runNumid <= 0) goto OnErrorExit; + + // register mute/unmute as a pause/resume control + error = AlsaCtlRegister(source, captureDev, runNumid); + if (error) goto OnErrorExit; + + // create stream and delay pcm openning until vol control is created + char volName[ALSA_CARDID_MAX_LEN]; + snprintf(volName, sizeof (volName), "vol-%s", sndStream[idx].uid); + AlsaPcmInfoT *streamPcm = AlsaCreateSoftvol(source, &sndStream[idx], captureDev, volName, VOL_CONTROL_MAX, 0); + if (!streamPcm) { + AFB_ApiError(source->api, "SndStreams: mixer=%s%s(pcm) fail to create stream", mixerHandle->uid, sndStream[idx].uid); + goto OnErrorExit; + } + + // create volume control before softvol pcm is opened + int volNumid = AlsaCtlCreateControl(source, ctlDev, playbackDev, volName, streamPcm->params.channels, VOL_CONTROL_MIN, VOL_CONTROL_MAX, VOL_CONTROL_STEP, sndStream[idx].volume); + if (volNumid <= 0) goto OnErrorExit; + + // **** Fulup (would need some help to get automatic rate converter to work). + // // add a rate converter plugin to match stream params config + // char rateName[ALSA_CARDID_MAX_LEN]; + // snprintf(rateName, sizeof (rateName), "rate-%s", sndStream[idx].uid); + // AlsaPcmInfoT *ratePcm= AlsaCreateRate(source, rateName, streamPcm, 1); + // if (!ratePcm) { + // AFB_ApiError(source->api, "SndStreams: mixer=%s%s(pcm) fail to create rate converter", sndStream[idx].uid); + // goto OnErrorExit; + // } + + // everything is not ready to open capture pcm + error = snd_pcm_open(&streamPcm->handle, sndStream[idx].uid, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (error) { + AFB_ApiError(source->api, "SndStreams: mixer=%s%s(pcm) fail to open capture", mixerHandle->uid, sndStream[idx].uid); + goto OnErrorExit; + } + + // capture stream inherit channel from targeted zone + captureDev->ccount = streamPcm->ccount; + streamPcm->params.channels = streamPcm->ccount; + + // start stream pcm copy (at this both capture & sink pcm should be open) + error = AlsaPcmCopy(source, captureDev, streamPcm, &streamPcm->params); + if (error) goto OnErrorExit; + + // retrieve active/pause control and set PCM status accordingly + error = AlsaCtlNumidGetLong(source, ctlDev, captureDev->numid, &value); + if (error) goto OnErrorExit; + + // toggle pause/resume (should be done after pcm_start) + if ((error = snd_pcm_pause(captureDev->handle, !value)) < 0) { + AFB_ApiError(source->api, "SndStreams: mixer=%s [capture=%s] fail to pause error=%s", mixerHandle->uid, captureDev->cardid, snd_strerror(error)); + goto OnErrorExit; + } + + // prepare response for application + playbackDev->device = ctlLoop->playback; + error = AlsaByPathDevid(source, playbackDev); + + error += wrap_json_pack(¶msJ, "{si si si si}", "rate", streamPcm->params.rate, "channels", streamPcm->params.channels, "format", streamPcm->params.format, "access", streamPcm->params.access); + error += wrap_json_pack(&streamJ, "{ss ss si si so}", "uid", streamPcm->uid, "alsa", playbackDev->cardid, "volid", volNumid, "runid", runNumid, "params", paramsJ); + error += json_object_array_add(*responseJ, streamJ); + if (error) { + AFB_ApiError(source->api, "SndStreams: mixer=%s stream=%s fail to prepare response", mixerHandle->uid, captureDev->cardid); + goto OnErrorExit; + } + + // create a dedicated verb for this stream compose of mixeruid/streamuid + apiHandleT *apiHandle = calloc(1, sizeof (apiHandleT)); + char apiVerb[128]; + error = snprintf(apiVerb, sizeof (apiVerb), "%s/%s", mixerHandle->uid, sndStream[idx].uid); + if (error == sizeof (apiVerb)) { + AFB_ApiError(source->api, "SndStreams mixer=%s fail to register Stream API too long %s/%s", mixerHandle->uid, mixerHandle->uid, sndStream[idx].uid); + return -1; + } + + apiHandle->mixer = mixerHandle; + apiHandle->streams = &sndStream[idx]; + apiHandle->verb = strdup(apiVerb); + error = afb_dynapi_add_verb(source->api, apiHandle->verb, sndStream[idx].info, StreamApiVerbCB, mixerHandle, NULL, 0); + if (error) { + AFB_ApiError(source->api, "SndStreams mixer=%s fail to register API verb=%s", mixerHandle->uid, apiHandle->verb); + return -1; + } + + // free tempry resource + snd_ctl_close(ctlDev); + + // Debug Alsa Config + //AlsaDumpElemConfig (source, "\n\nAlsa_Config\n------------\n", "pcm"); + //AlsaDumpPcmInfo(source, "\n\nPcm_config\n-----------\n", streamPcm->handle); + + AFB_ApiNotice(source->api, "SndStreams: mixer=%s stream=%s OK reponse=%s\n", mixerHandle->uid, streamPcm->uid, json_object_get_string(streamJ)); + } + + return 0; + +OnErrorExit: + return -1; +} \ No newline at end of file diff --git a/plugins/alsa/alsa-api-zones.c b/plugins/alsa/alsa-api-zones.c new file mode 100644 index 0000000..de834df --- /dev/null +++ b/plugins/alsa/alsa-api-zones.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author Fulup Ar Foll + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#define _GNU_SOURCE // needed for vasprintf + +#include "alsa-softmixer.h" +#include + +// 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, "ProcessOneZone 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; +} + +PUBLIC int SndZones(CtlSourceT *source, json_object *argsJ) { + SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; + AlsaSndZoneT *zones=NULL; + int error; + size_t count; + + assert(mixerHandle); + + if (mixerHandle->routes) { + AFB_ApiError(source->api, "SndZones: mixer=%s Zones already registered %s", mixerHandle->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + switch (json_object_get_type(argsJ)) { + case json_type_object: + count = 1; + zones = calloc(count + 1, sizeof (AlsaSndZoneT)); + error = ProcessOneZone(source, argsJ, &zones[0]); + if (error) { + AFB_ApiError(source->api, "SndZones: mixer=%s invalid zone= %s", mixerHandle->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + break; + + case json_type_array: + count = json_object_array_length(argsJ); + zones = 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, &zones[idx]); + if (error) { + AFB_ApiError(source->api, "SndZones: mixer=%s invalid zone= %s", mixerHandle->uid, json_object_get_string(sndZoneJ)); + goto OnErrorExit; + } + } + break; + default: + AFB_ApiError(source->api, "SndZones: mixer=%s invalid argsJ= %s", mixerHandle->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + // register routed into global softmixer handle + mixerHandle->routes= calloc(count + 1, sizeof (AlsaPcmInfoT*)); + + // instantiate one route PCM per zone with multi plugin as slave + for (int idx = 0; zones[idx].uid != NULL; idx++) { + mixerHandle->routes[idx] = AlsaCreateRoute(source, &zones[idx], 0); + if (!mixerHandle->routes[idx]) { + AFB_ApiNotice(source->api, "SndZones: mixer=%s fail to create route zone=%s", mixerHandle->uid, zones[idx].uid); + goto OnErrorExit; + } + } + + free (zones); + return 0; + +OnErrorExit: + if (zones) free(zones); + 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 9a0b844..f224f92 100644 --- a/plugins/alsa/alsa-core-ctl.c +++ b/plugins/alsa/alsa-core-ctl.c @@ -170,6 +170,7 @@ OnErrorExit: return NULL; } + STATIC int CtlElemIdGetNumid(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, int *numid) { snd_ctl_elem_info_t *elemInfo; diff --git a/plugins/alsa/alsa-plug-multi.c b/plugins/alsa/alsa-plug-multi.c index 3e82e1e..0dd417d 100644 --- a/plugins/alsa/alsa-plug-multi.c +++ b/plugins/alsa/alsa-plug-multi.c @@ -23,15 +23,17 @@ ALSA_PLUG_PROTO(multi); PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmUid, int open) { - + SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; 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) { + assert(mixerHandle); + + AlsaPcmInfoT* pcmSlaves=mixerHandle->backend; + if (!pcmSlaves) { AFB_ApiError(source->api, "AlsaCreateMulti: No Sound Card find [should register snd_cards first]"); goto OnErrorExit; } diff --git a/plugins/alsa/alsa-plug-route.c b/plugins/alsa/alsa-plug-route.c index 8fa5de8..d2ca223 100644 --- a/plugins/alsa/alsa-plug-route.c +++ b/plugins/alsa/alsa-plug-route.c @@ -48,17 +48,20 @@ OnErrorExit: } PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone, int open) { - + SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; 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; + + assert(mixerHandle); - AlsaPcmInfoT *pcmBackend = Softmixer->sndcardCtl; - AlsaPcmInfoT* pcmSlave=Softmixer->multiPcm; + AlsaPcmInfoT *pcmBackend = mixerHandle->backend; + AlsaPcmInfoT* pcmSlave=mixerHandle->multiPcm; if (!pcmBackend || !pcmSlave) { - AFB_ApiError(source->api, "AlsaCreateRoute:zone(%s)(zone) No Sound Card Ctl find [should register snd_cards first]", zone->uid); + AFB_ApiError(source->api, "AlsaCreateRoute: mixer=%s zone(%s)(zone) No Sound Card Ctl find [should register snd_cards first]" + , mixerHandle->uid, zone->uid); goto OnErrorExit; } diff --git a/plugins/alsa/alsa-plug-vol.c b/plugins/alsa/alsa-plug-vol.c index dc600f9..09bc51a 100644 --- a/plugins/alsa/alsa-plug-vol.c +++ b/plugins/alsa/alsa-plug-vol.c @@ -36,30 +36,32 @@ STATIC AlsaPcmInfoT* SlaveZoneByUid(CtlSourceT *source, AlsaPcmInfoT **pcmZones return NULL; } -PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *ctlControl, const char* ctlName, int max, int open) { - +PUBLIC AlsaPcmInfoT* AlsaCreateSoftvol(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *ctlControl, const char* ctlName, int max, int open) { + SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; snd_config_t *streamConfig, *elemConfig, *slaveConfig, *controlConfig,*pcmConfig; int error = 0; AlsaPcmInfoT *pcmPlug= calloc(1,sizeof(AlsaPcmInfoT)); + assert (mixerHandle); + // assert static/global softmixer handle get requited info - AlsaSndLoopT *ctlLoop = Softmixer->loopCtl; + AlsaSndLoopT *ctlLoop = mixerHandle->loop; if (!ctlLoop) { - AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) No Loop found [should register snd_loop first]",stream->uid); + AFB_ApiError(source->api, "AlsaCreateSoftvol:%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; + AlsaPcmInfoT **pcmZones = mixerHandle->routes; if (!pcmZones) { - AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) No Zone found [should register snd_zones first]", stream->uid); + AFB_ApiError(source->api, "AlsaCreateSoftvol:%s(stream) No Zone found [should register snd_zones first]", stream->uid); goto OnErrorExit; } // search for target zone uid AlsaPcmInfoT *pcmSlave= SlaveZoneByUid (source, pcmZones, stream->zone); if (!pcmSlave || !pcmSlave->uid) { - AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) fail to find Zone=%s", stream->uid, stream->zone); + AFB_ApiError(source->api, "AlsaCreateSoftvol:%s(stream) fail to find Zone=%s", stream->uid, stream->zone); goto OnErrorExit; } @@ -102,24 +104,24 @@ PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream if (open) 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)); + AFB_ApiError(source->api, "AlsaCreateSoftvol:%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); + AFB_ApiError(source->api, "AlsaCreateSoftvol:%s(stream) fail to add config", stream->uid); goto OnErrorExit; } // Debug config & pcm //AlsaDumpCtlConfig (source, "plug-stream", streamConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateStream:%s(stream) done\n", stream->uid); + AFB_ApiNotice(source->api, "AlsaCreateSoftvol:%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); + AFB_ApiNotice(source->api, "AlsaCreateSoftvol:%s(stream) OnErrorExit\n", stream->uid); return NULL; } \ No newline at end of file diff --git a/plugins/alsa/alsa-softmixer.c b/plugins/alsa/alsa-softmixer.c index 033e87b..4528087 100644 --- a/plugins/alsa/alsa-softmixer.c +++ b/plugins/alsa/alsa-softmixer.c @@ -24,12 +24,9 @@ CTLP_LUA_REGISTER("alsa-softmixer") -SoftMixerHandleT *Softmixer; - // Call at initialisation time CTLP_ONLOAD(plugin, callbacks) { - AFB_ApiDebug(plugin->api, "SoftMixer plugin: uid='%s' 'info='%s'", plugin->uid, plugin->info); - Softmixer = calloc(1, sizeof(SoftMixerHandleT)); + AFB_ApiDebug(plugin->api, "SoftMixer Plugin Registered: uid='%s' 'info='%s'", plugin->uid, plugin->info); return NULL; } diff --git a/plugins/alsa/alsa-softmixer.h b/plugins/alsa/alsa-softmixer.h index e2c9370..7146e78 100644 --- a/plugins/alsa/alsa-softmixer.h +++ b/plugins/alsa/alsa-softmixer.h @@ -24,13 +24,14 @@ #include #include #include -#include #include +#include +#include +#include #include "ctl-plugin.h" #include "wrap-json.h" -#include #ifndef PUBLIC #define PUBLIC @@ -104,10 +105,9 @@ typedef struct { AlsaPcmInfoT *subdevs; } AlsaSndLoopT; - - typedef struct { const char *uid; + const char *info; const char *zone; int volume; int mute; @@ -116,23 +116,20 @@ typedef struct { } AlsaSndStreamT; typedef struct { - AlsaSndLoopT *loopCtl; - AlsaPcmInfoT *sndcardCtl; + const char *uid; + const char *info; + AlsaSndLoopT *loop; + AlsaPcmInfoT *backend; AlsaPcmInfoT *multiPcm; - AlsaPcmInfoT **zonePcms; + AlsaPcmInfoT **routes; } SoftMixerHandleT; -extern SoftMixerHandleT *Softmixer; - // alsa-utils-bypath.c PUBLIC snd_ctl_card_info_t* AlsaByPathInfo(CtlSourceT *source, const char *control); PUBLIC AlsaPcmInfoT* AlsaByPathOpenPcm(CtlSourceT *source, AlsaPcmInfoT *dev, snd_pcm_stream_t direction); PUBLIC snd_ctl_t *AlsaByPathOpenCtl(CtlSourceT *source, AlsaPcmInfoT *dev); PUBLIC int AlsaByPathDevid(CtlSourceT *source, AlsaPcmInfoT *dev); -// alsa-api-*.c -PUBLIC int ProcessSndParams(CtlSourceT *source, const char* uid, json_object *paramsJ, AlsaPcmHwInfoT *params); - // 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); @@ -168,7 +165,14 @@ PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pc PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave, int open); PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmName, int open); PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone, int open); -PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *ctlControl, const char* ctlName, int max, int open); +PUBLIC AlsaPcmInfoT* AlsaCreateSoftvol(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *ctlControl, const char* ctlName, int max, int open); PUBLIC AlsaPcmInfoT* AlsaCreateRate(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave, int open); +// alsa-api-* +PUBLIC int ProcessSndParams(CtlSourceT *source, const char* uid, json_object *paramsJ, AlsaPcmHwInfoT *params); +PUBLIC int SndFrontend (CtlSourceT *source, json_object *argsJ); +PUBLIC int SndBackend (CtlSourceT *source, json_object *argsJ); +PUBLIC int SndZones (CtlSourceT *source, json_object *argsJ); +PUBLIC int SndStreams(CtlSourceT *source, json_object *argsJ, json_object **responseJ); + #endif \ No newline at end of file -- cgit 1.2.3-korg