From bbaf42c6b02ca5d1bdceb7e215807abdbf8cd61d Mon Sep 17 00:00:00 2001 From: Fulup Ar Foll Date: Mon, 4 Jun 2018 00:33:34 +0200 Subject: Move to Dynamic Mixer API (work in progress) --- plugins/alsa/alsa-api-backend.c | 220 ------------------ plugins/alsa/alsa-api-frontend.c | 245 -------------------- plugins/alsa/alsa-api-loop.c | 205 +++++++++++++++++ plugins/alsa/alsa-api-mixer.c | 359 +++++++++++++++++------------ plugins/alsa/alsa-api-pcm.c | 304 +++++++++++++++++++++++++ plugins/alsa/alsa-api-ramp.c | 114 ++++++++++ plugins/alsa/alsa-api-sink.c | 111 +++++++++ plugins/alsa/alsa-api-source.c | 84 +++++++ plugins/alsa/alsa-api-streams.c | 475 ++++++++++++++++++++------------------- plugins/alsa/alsa-api-zones.c | 196 +++++++++------- plugins/alsa/alsa-core-ctl.c | 346 ++++++++++++---------------- plugins/alsa/alsa-core-pcm.c | 103 +++++---- plugins/alsa/alsa-effect-ramp.c | 144 +++++++----- plugins/alsa/alsa-plug-dmix.c | 35 ++- plugins/alsa/alsa-plug-multi.c | 106 --------- plugins/alsa/alsa-plug-rate.c | 51 ++--- plugins/alsa/alsa-plug-route.c | 175 +++++++++------ plugins/alsa/alsa-plug-vol.c | 83 +++---- plugins/alsa/alsa-softmixer.h | 222 +++++++++++------- plugins/alsa/alsa-utils-bypath.c | 104 +++------ plugins/alsa/alsa-utils-dump.c | 70 +++--- 21 files changed, 2069 insertions(+), 1683 deletions(-) delete mode 100644 plugins/alsa/alsa-api-backend.c delete mode 100644 plugins/alsa/alsa-api-frontend.c create mode 100644 plugins/alsa/alsa-api-loop.c create mode 100644 plugins/alsa/alsa-api-pcm.c create mode 100644 plugins/alsa/alsa-api-ramp.c create mode 100644 plugins/alsa/alsa-api-sink.c create mode 100644 plugins/alsa/alsa-api-source.c delete mode 100644 plugins/alsa/alsa-plug-multi.c (limited to 'plugins/alsa') diff --git a/plugins/alsa/alsa-api-backend.c b/plugins/alsa/alsa-api-backend.c deleted file mode 100644 index 85d9566..0000000 --- a/plugins/alsa/alsa-api-backend.c +++ /dev/null @@ -1,220 +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, AlsaPcmChannelT *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 (AlsaPcmChannelT)); - 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 (AlsaPcmChannelT)); - 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 deleted file mode 100644 index 11b8a7e..0000000 --- a/plugins/alsa/alsa-api-frontend.c +++ /dev/null @@ -1,245 +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 ProcessOneRamp(CtlSourceT *source, const char* uid, json_object *rampJ, AlsaVolRampT *ramp) { - const char*rampUid; - - int error = wrap_json_unpack(rampJ, "{ss,si,si,si !}" - , "uid", &rampUid - , "delay", &ramp->delay - , "up", &ramp->stepUp - , "down", &ramp->stepDown - ); - if (error) goto OnErrorExit; - - ramp->delay=ramp->delay*100; // move from ms to us - ramp->uid = strdup(rampUid); - return 0; - -OnErrorExit: - AFB_ApiError(source->api, "ProcessOneRamp: sndcard=%s ramps: missing (uid||delay|up|down) json=%s", uid, json_object_get_string(rampJ)); - return -1; -} - -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, *rampsJ = NULL; - int error; - - error = wrap_json_unpack(loopJ, "{ss,s?s,s?s,s?i,s?o,so,s?o,s?o !}" - , "uid", &loop->uid - , "devpath", &loop->devpath - , "cardid", &loop->cardid - , "cardidx", &loop->cardidx - , "devices", &devicesJ - , "subdevs", &subdevsJ - , "params", ¶msJ - , "ramps", &rampsJ - ); - 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; - } - - // Fake a sound card to check if loop is a valid Alsa snd driver - AlsaPcmInfoT sndLoop; - sndLoop.uid = loop->uid; - sndLoop.devpath = loop->devpath; - sndLoop.cardid = loop->cardid; - sndLoop.device = 0; - sndLoop.subdev = 0; - error = AlsaByPathDevid(source, &sndLoop); - if (error) { - AFB_ApiError(source->api, "ProcessOneLoop: loop=%s not found config=%s", loop->uid, json_object_get_string(loopJ)); - goto OnErrorExit; - } - loop->uid = sndLoop.uid; - loop->devpath = sndLoop.devpath; - loop->cardid = sndLoop.cardid; - loop->cardidx = sndLoop.cardidx; - loop->registry= calloc (1,sizeof(RegistryHandleT)); - - // process volume ramps - if (rampsJ) { - int rcount; - switch (json_object_get_type(rampsJ)) { - case json_type_object: - rcount = 1; - loop->ramps = calloc(rcount+1, sizeof (AlsaVolRampT)); - error = ProcessOneRamp(source, loop->uid, rampsJ, &loop->ramps[0]); - if (error) goto OnErrorExit; - break; - case json_type_array: - rcount = (int) json_object_array_length(rampsJ); - loop->ramps = calloc(rcount+1, sizeof (AlsaVolRampT)); - for (int idx = 0; idx < rcount; idx++) { - json_object *rampJ = json_object_array_get_idx(rampsJ, idx); - error = ProcessOneRamp(source, loop->uid, rampJ, &loop->ramps[idx]); - if (error) goto OnErrorExit; - } - break; - default: - AFB_ApiError(source->api, "ProcessOneLoop:%s invalid ramps=%s", loop->uid, json_object_get_string(rampsJ)); - goto OnErrorExit; - } - } - - // 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 *mixer = (SoftMixerHandleT*) source->context; - int error; - - assert(mixer); - - if (mixer->frontend) { - AFB_ApiError(source->api, "SndFrontend: mixer=%s SndFrontend already declared %s", mixer->uid, json_object_get_string(argsJ)); - goto OnErrorExit; - } - - mixer->frontend = 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", mixer->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", mixer->uid, json_object_get_string(argsJ)); - goto OnErrorExit; - } - - error = ProcessOneLoop(source, argsJ, mixer->frontend); - if (error) { - AFB_ApiError(source->api, "SndFrontend: mixer=%s invalid object= %s", mixer->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-loop.c b/plugins/alsa/alsa-api-loop.c new file mode 100644 index 0000000..9a746ea --- /dev/null +++ b/plugins/alsa/alsa-api-loop.c @@ -0,0 +1,205 @@ +/* + * 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 + +PUBLIC AlsaLoopSubdevT *ApiLoopFindSubdev(SoftMixerT *mixer, const char *streamUid, const char *targetUid, AlsaSndLoopT **loop) { + + // Either allocate a free loop subdev or search for a specific targetUid when specified + if (targetUid) { + for (int idx = 0; mixer->loops[idx]; idx++) { + for (int jdx = 0; jdx < mixer->loops[idx]->scount; jdx++) { + if (mixer->loops[idx]->subdevs[jdx]->uid && !strcasecmp(mixer->loops[idx]->subdevs[jdx]->uid, targetUid)) { + *loop = mixer->loops[idx]; + return mixer->loops[idx]->subdevs[jdx]; + } + } + } + } else { + for (int idx = 0; mixer->loops[idx]; idx++) { + for (int jdx = 0; mixer->loops[idx]->subdevs[jdx]; jdx++) { + if (!mixer->loops[idx]->subdevs[jdx]->uid) { + mixer->loops[idx]->subdevs[jdx]->uid = streamUid; + *loop = mixer->loops[idx]; + return mixer->loops[idx]->subdevs[jdx]; + } + } + } + } + return NULL; +} + +STATIC AlsaLoopSubdevT *ProcessOneSubdev(SoftMixerT *mixer, AlsaSndLoopT *loop, json_object *subdevJ) { + AlsaLoopSubdevT *subdev = calloc(1, sizeof (AlsaPcmCtlT)); + + int error = wrap_json_unpack(subdevJ, "{s?s, si,si,s?o !}" + , "uid", &subdev->uid + , "subdev", &subdev->index + , "numid", &subdev->numid + ); + if (error) { + AFB_ApiError(mixer->api, "ProcessOneSubdev: loop=%s missing (uid|subdev|numid) error=%s json=%s", loop->uid, wrap_json_get_error_string(error),json_object_get_string(subdevJ)); + goto OnErrorExit; + } + + // subdev with no UID are dynamically attached + if (subdev->uid) subdev->uid = strdup(subdev->uid); + + // create loop subdev entry point with cardidx+device+subdev in order to open subdev and not sndcard + AlsaDevInfoT loopSubdev; + loopSubdev.devpath=NULL; + loopSubdev.cardid=NULL; + loopSubdev.cardidx = loop->sndcard->cid.cardidx; + loopSubdev.device = loop->capture; + loopSubdev.subdev = subdev->index; + + // assert we may open this loopback subdev in capture mode + AlsaPcmCtlT *pcmInfo = AlsaByPathOpenPcm(mixer, &loopSubdev, SND_PCM_STREAM_CAPTURE); + if (!pcmInfo) goto OnErrorExit; + + // free PCM as we only open loop to assert it's a valid capture device + snd_pcm_close(pcmInfo->handle); + free(pcmInfo); + + return subdev; + +OnErrorExit: + return NULL; +} + +STATIC AlsaSndLoopT *AttachOneLoop(SoftMixerT *mixer, const char *uid, json_object *argsJ) { + AlsaSndLoopT *loop = calloc(1, sizeof (AlsaSndLoopT)); + json_object *subdevsJ = NULL, *devicesJ = NULL; + int error; + + loop->sndcard = (AlsaSndCtlT*) calloc(1, sizeof (AlsaSndCtlT)); + error = wrap_json_unpack(argsJ, "{ss,s?s,s?s,so,so !}" + , "uid", &loop->uid + , "path", &loop->sndcard->cid.devpath + , "cardid", &loop->sndcard->cid.cardid + , "devices", &devicesJ + , "subdevs", &subdevsJ + ); + if (error || !loop->uid || !subdevsJ || (!loop->sndcard->cid.devpath && !loop->sndcard->cid.cardid)) { + AFB_ApiNotice(mixer->api, "AttachOneLoop mixer=%s hal=%s missing 'uid|path|cardid|devices|subdevs' error=%s args=%s", mixer->uid, uid, wrap_json_get_error_string(error),json_object_get_string(argsJ)); + goto OnErrorExit; + } + + // try to open sound card control interface + loop->sndcard->ctl = AlsaByPathOpenCtl(mixer, loop->uid, loop->sndcard); + if (!loop->sndcard->ctl) { + AFB_ApiError(mixer->api, "AttachOneLoop mixer=%s hal=%s Fail open sndcard loop=%s devpath=%s cardid=%s (please check 'modprobe snd_aloop')", mixer->uid, uid, loop->uid, loop->sndcard->cid.devpath, loop->sndcard->cid.cardid); + goto OnErrorExit; + } + + // 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(mixer->api, "AttachOneLoop mixer=%s hal=%s Loop=%s missing 'capture|playback' error=%s devices=%s", mixer->uid, uid, loop->uid, wrap_json_get_error_string(error),json_object_get_string(devicesJ)); + goto OnErrorExit; + } + } + + switch (json_object_get_type(subdevsJ)) { + case json_type_object: + loop->scount = 1; + loop->subdevs = calloc(2, sizeof (void*)); + loop->subdevs[0] = ProcessOneSubdev(mixer, loop, subdevsJ); + if (!loop->subdevs[0]) goto OnErrorExit; + break; + case json_type_array: + loop->scount = (int) json_object_array_length(subdevsJ); + loop->subdevs = calloc(loop->scount + 1, sizeof (void*)); + for (int idx = 0; idx < loop->scount; idx++) { + json_object *subdevJ = json_object_array_get_idx(subdevsJ, idx); + loop->subdevs[idx] = ProcessOneSubdev(mixer, loop, subdevJ); + if (!loop->subdevs[idx]) goto OnErrorExit; + } + break; + default: + AFB_ApiError(mixer->api, "AttachOneLoop mixer=%s hal=%s Loop=%s invalid subdevs= %s", mixer->uid, uid, loop->uid, json_object_get_string(subdevsJ)); + goto OnErrorExit; + } + + // we may have to register up to 3 control per subdevice (vol, pause, actif) + loop->sndcard->registry = calloc(loop->scount * SMIXER_SUBDS_CTLS + 1, sizeof (RegistryEntryPcmT)); + loop->sndcard->rcount = loop->scount*SMIXER_SUBDS_CTLS; + + return loop; + +OnErrorExit: + return NULL; +} + +PUBLIC int ApiLoopAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ) { + + int index; + for (index = 0; index < mixer->max.loops; index++) { + if (!mixer->loops[index]) break; + } + + if (index == mixer->max.loops) { + AFB_ReqFailF(request, "too-small", "mixer=%s hal=%s max loop=%d argsJ= %s", mixer->uid, uid, mixer->max.loops, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + switch (json_object_get_type(argsJ)) { + size_t count; + + case json_type_object: + mixer->loops[index] = AttachOneLoop(mixer, uid, argsJ); + if (!mixer->loops[index]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s hal=%s invalid loop= %s", mixer->uid, uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + break; + + case json_type_array: + count = json_object_array_length(argsJ); + if (count > (mixer->max.loops - count)) { + AFB_ReqFailF(request, "too-small", "mixer=%s hal=%s max loop=%d argsJ= %s", mixer->uid, uid, mixer->max.loops, json_object_get_string(argsJ)); + goto OnErrorExit; + + } + + for (int idx = 0; idx < count; idx++) { + json_object *loopJ = json_object_array_get_idx(argsJ, idx); + mixer->loops[index + idx] = AttachOneLoop(mixer, uid, loopJ); + if (!mixer->loops[index + idx]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s hal=%s invalid loop= %s", mixer->uid, uid, json_object_get_string(loopJ)); + goto OnErrorExit; + } + } + break; + default: + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s hal=%s loops invalid argsJ= %s", mixer->uid, 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 index b910b9f..300dd6a 100644 --- a/plugins/alsa/alsa-api-mixer.c +++ b/plugins/alsa/alsa-api-mixer.c @@ -23,186 +23,271 @@ #include -// Fulup need to be cleanup with new controller version extern Lua2cWrapperT Lua2cWrap; -// API - -static void MixerApiVerbCB(AFB_ReqT request) { - json_object *valueJ, *backendJ = NULL, *frontendJ = NULL, *zonesJ = NULL, *streamsJ = NULL, *listJ = NULL; - // retrieve action handle from request and execute the request - json_object *argsJ = afb_request_json(request); - json_object *responseJ = json_object_new_object(); - - SoftMixerHandleT *mixer = (SoftMixerHandleT*) afb_request_get_vcbdata(request); +static void MixerRemoveVerb(AFB_ReqT request) { + SoftMixerT *mixer = (SoftMixerT*) afb_request_get_vcbdata(request); int error; - int delete = 0; - - CtlSourceT *source = alloca(sizeof (CtlSourceT)); - source->uid = mixer->uid; - source->api = request->dynapi; - source->request = request; - source->context = mixer; - - error = wrap_json_unpack(argsJ, "{s?b,s?o,s?o,s?o,s?o,s?o !}" - , "delete", &delete - , "list", &listJ - , "backend", &backendJ - , "frontend", &frontendJ - , "zones", &zonesJ - , "streams", &streamsJ - ); - if (error) { - AFB_ReqFailF(request, "invalid-syntax", "request missing 'uid|list|backend|frontend|zones|streams' mixer=%s", json_object_get_string(argsJ)); - goto OnErrorExit; - } - - // Free attached resources and free mixer - if (delete) { - for (int idx; mixer->streams[idx].uid; idx++) { - AlsaLoopStreamT *stream = &mixer->streams[idx]; - - AFB_ApiNotice(source->api, "cleaning mixer=%s stream=%s", mixer->uid, stream->uid); - - error = pthread_cancel(stream->copy.thread); - if (error) { - AFB_ReqFailF(request, "internal-error", "Fail to kill audio-stream threads mixer=%s", mixer->uid); - goto OnErrorExit; - } - - char apiStreamVerb[128]; - error = snprintf(apiStreamVerb, sizeof (apiStreamVerb), "%s/%s", mixer->uid, stream->uid); - if (error == sizeof (apiStreamVerb)) { - AFB_ApiError(source->api, "LoopStreams mixer=%s fail to Registry Stream API too long %s/%s", mixer->uid, mixer->uid, stream->uid); - goto OnErrorExit; - } - error = afb_dynapi_sub_verb(source->api, apiStreamVerb); - if (error) { - AFB_ApiError(source->api, "fail to Clean API verb=%s", apiStreamVerb); - goto OnErrorExit; - } + for (int idx; mixer->streams[idx]->uid; idx++) { + AlsaStreamAudioT *stream = mixer->streams[idx]; - // free audio-stream dynamic structures - snd_pcm_close(mixer->streams[idx].copy.pcmIn); - snd_pcm_close(mixer->streams[idx].copy.pcmOut); - if (stream->copy.evtsrc) sd_event_source_unref(stream->copy.evtsrc); - if (stream->copy.sdLoop) sd_event_unref(stream->copy.sdLoop); + AFB_ApiNotice(mixer->api, "cleaning mixer=%s stream=%s", mixer->uid, stream->uid); + error = pthread_cancel(stream->copy->thread); + if (error) { + AFB_ReqFailF(request, "internal-error", "Fail to kill audio-stream threads mixer=%s", mixer->uid); + goto OnErrorExit; } - // registry is attached to frontend - if (mixer->frontend->registry)free(mixer->frontend->registry); - - error = afb_dynapi_sub_verb(source->api, mixer->uid); + error = afb_dynapi_sub_verb(mixer->api, stream->uid); if (error) { - AFB_ApiError(source->api, "fail to Clean API verb=%s", mixer->uid); + AFB_ReqFailF(request, "internal-error", "Mixer=%s fail to remove verb=%s error=%s", mixer->uid, stream->uid, strerror(error)); goto OnErrorExit; } - // finally free mixer handle - free(mixer); - responseJ = json_object_new_string("Fulup: delete might not clean everything properly"); - goto OnSuccessExit; + // free audio-stream dynamic structures + snd_pcm_close(mixer->streams[idx]->copy->pcmIn); + snd_pcm_close(mixer->streams[idx]->copy->pcmOut); + if (stream->copy->evtsrc) sd_event_source_unref(stream->copy->evtsrc); + if (stream->copy->sdLoop) sd_event_unref(stream->copy->sdLoop); } - if (listJ) { - int streams = 0, quiet = 0, backend = 0, frontend = 0, zones = 0; + // // (Fulup to be Done) registry is attached to source + // if (mixer->sources) ApiSourcFree (mixer); + // if (mixer->sinks) ApiSinkFree (mixer); + // if (mixer->loops) ApiLoopFree (mixer); + // if (mixer->ramps) ApiRampFree (mixer); + // if (mixer->zones) ApiZoneFree (mixer); + + // finally free mixer handle + free(mixer); + AFB_ReqSucess(request, NULL, "Fulup: delete might not clean everything properly"); + + return; + +OnErrorExit: + AFB_ReqFail(request, "internal-error", "fail to delete mixer"); +} - error = wrap_json_unpack(listJ, "{s?b,s?b,s?b,s?b,s?b !}" +STATIC void MixerInfoVerb(AFB_ReqT request) { + + SoftMixerT *mixer = (SoftMixerT*) afb_request_get_vcbdata(request); + json_object *argsJ = afb_request_json(request); + int error, stream = 0, quiet = 0, backend = 0, source = 0, zones = 0; + + if (json_object_get_type(argsJ) == json_type_null) { + stream = 1; + } else { + error = wrap_json_unpack(argsJ, "{s?b,s?b,s?b,s?b,s?b !}" , "quiet", &quiet - , "streams", &streams + , "stream", &stream , "backend", &backend - , "frontend", &frontend + , "source", &source , "zones", &zones ); if (error) { - AFB_ReqFailF(request, "invalid-syntax", "list missing 'uid|backend|frontend|zones|streams' list=%s", json_object_get_string(listJ)); + AFB_ReqFailF(request, "invalid-syntax", "list missing 'quiet|stream|backend|source' argsJ=%s", json_object_get_string(argsJ)); goto OnErrorExit; } + } + json_object *responseJ = json_object_new_object(); - if (streams) { - streamsJ = json_object_new_array(); - - AlsaLoopStreamT *streams = mixer->streams; - for (int idx = 0; streams[idx].uid; idx++) { - if (quiet) { - json_object_array_add(streamsJ, json_object_new_string(streams[idx].uid)); - } else { - json_object *numidJ; - wrap_json_pack(&numidJ, "{si,si}" - , "volume", streams[idx].volume - , "mute", streams[idx].mute - ); - wrap_json_pack(&valueJ, "{ss,so}" - , "uid", streams[idx].uid - , "numid", numidJ - ); - json_object_array_add(streamsJ, valueJ); - AFB_ApiWarning(request->dynapi, "stream=%s", json_object_get_string(streamsJ)); - } - + if (stream) { + json_object *streamsJ = json_object_new_array(); + json_object *valueJ; + + AlsaStreamAudioT **streams = mixer->streams; + for (int idx = 0; streams[idx]->uid; idx++) { + if (quiet) { + json_object_array_add(streamsJ, json_object_new_string(streams[idx]->uid)); + } else { + json_object *numidJ; + wrap_json_pack(&numidJ, "{si,si}" + , "volume", streams[idx]->volume + , "mute", streams[idx]->mute + ); + wrap_json_pack(&valueJ, "{ss,so}" + , "uid", streams[idx]->uid + , "numid", numidJ + ); + json_object_array_add(streamsJ, valueJ); + AFB_ApiWarning(request->dynapi, "stream=%s", json_object_get_string(streamsJ)); } - json_object_object_add(responseJ, "streams", streamsJ); - } - if (backend || frontend || zones) { - AFB_ReqFailF(request, "not implemented", "(Fulup) list action Still To Be Done"); - goto OnErrorExit; } + json_object_object_add(responseJ, "streams", streamsJ); + } + + if (backend || source || zones) { + AFB_ReqFailF(request, "not implemented", "(Fulup) list action Still To Be Done"); + goto OnErrorExit; + } + + AFB_ReqSucess(request, responseJ, NULL); + return; + +OnErrorExit: + AFB_ReqFail(request, "internal-error", "fail to get mixer info"); + +} + +STATIC void MixerAttachVerb(AFB_ReqT request) { + SoftMixerT *mixer = (SoftMixerT*) afb_request_get_vcbdata(request); + const char *uid=NULL; + json_object *playbackJ = NULL, *captureJ = NULL, *zonesJ = NULL, *streamsJ = NULL, *rampsJ = NULL, *loopsJ = NULL; + json_object *argsJ = afb_request_json(request); + json_object *responseJ; + int error; + + error = wrap_json_unpack(argsJ, "{ss,s?o,s?o,s?o,s?o,s?o,s?o !}" + , "uid", &uid + , "ramps", &rampsJ + , "playbacks", &playbackJ + , "captures", &captureJ + , "loops", &loopsJ + , "zones", &zonesJ + , "streams", &streamsJ + ); + if (error) { + AFB_ApiError(mixer->api, "MixerAttachVerb: invalid-syntax mixer=%s error=%s args=%s", mixer->uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s missing 'uid|ramps|playbacks|captures|zones|streams' error=%s args=%s", mixer->uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); + goto OnErrorExit; + } - AFB_ReqSucess(request, responseJ, NULL); - return; + if (playbackJ) { + error = ApiSinkAttach(mixer, request, uid, playbackJ); + if (error) goto OnErrorExit; } - if (backendJ) { - error = SndBackend(source, backendJ); + if (captureJ) { + error = ApiSourceAttach(mixer, request, uid, captureJ); if (error) goto OnErrorExit; } - if (frontendJ) { - error = SndFrontend(source, frontendJ); + if (loopsJ) { + error = ApiLoopAttach(mixer, request, uid, loopsJ); if (error) goto OnErrorExit; } if (zonesJ) { - error = SndZones(source, zonesJ); + error = ApiZoneAttach(mixer, request, uid, zonesJ); + if (error) goto OnErrorExit; + } + + if (rampsJ) { + error = ApiRampAttach(mixer, request, uid, rampsJ); if (error) goto OnErrorExit; } if (streamsJ) { - error = LoopStreams(source, streamsJ, &responseJ); + error = ApiStreamAttach(mixer, request, uid, streamsJ, &responseJ); if (error) goto OnErrorExit; } -OnSuccessExit: - AFB_ReqSucess(request, responseJ, mixer->uid); + AFB_ReqNotice(request, "**** mixer=%s response=%s", json_object_get_string(responseJ), mixer->uid); + AFB_ReqSucess(request, NULL, mixer->uid); return; OnErrorExit: return; } +STATIC void MixerPingVerb(AFB_ReqT request) { + static int count = 0; + count++; + AFB_ReqNotice(request, "Controller:ping count=%d", count); + AFB_ReqSucess(request, json_object_new_int(count), NULL); + + return; +} + +STATIC void MixerEventCB(AFB_ApiT api, const char *evtLabel, struct json_object *eventJ) { + + SoftMixerT *mixer = (SoftMixerT*) afb_dynapi_get_userdata(api); + assert(mixer); + + AFB_ApiNotice(api, "Mixer=%s Received event=%s, eventJ=%s", mixer->uid, evtLabel, json_object_get_string(eventJ)); +} + +// Every HAL export the same API & Interface Mapping from SndCard to AudioLogic is done through alsaHalSndCardT +STATIC AFB_ApiVerbs CtrlApiVerbs[] = { + /* VERB'S NAME FUNCTION TO CALL SHORT DESCRIPTION */ + { .verb = "ping", .callback = MixerPingVerb, .info = "ping count test"}, + { .verb = "attach", .callback = MixerAttachVerb, .info = "attach resources to mixer"}, + { .verb = "remove", .callback = MixerRemoveVerb, .info = "remove existing mixer streams, zones, ..."}, + { .verb = "info", .callback = MixerInfoVerb, .info = "list existing mixer streams, zones, ..."}, + { .verb = NULL} /* marker for end of the array */ +}; + +STATIC int LoadStaticVerbs(SoftMixerT *mixer, AFB_ApiVerbs *verbs) { + int errcount = 0; + + for (int idx = 0; verbs[idx].verb; idx++) { + errcount += afb_dynapi_add_verb(mixer->api, CtrlApiVerbs[idx].verb, CtrlApiVerbs[idx].info, CtrlApiVerbs[idx].callback, (void*) mixer, CtrlApiVerbs[idx].auth, 0); + } + + return errcount; +}; + +STATIC int MixerInitCB(AFB_ApiT api) { + + SoftMixerT *mixer = (SoftMixerT*) afb_dynapi_get_userdata(api); + assert(mixer); + + // attach AFB mainloop to mixer + mixer->sdLoop = AFB_GetEventLoop(api); + + + AFB_ApiNotice(api, "MixerInitCB API=%s activated info=%s", mixer->uid, mixer->info); + + return 0; +} + +STATIC int MixerApiCB(void* handle, AFB_ApiT api) { + SoftMixerT *mixer = (SoftMixerT*) handle; + + mixer->api= api; + afb_dynapi_set_userdata(api, mixer); + afb_dynapi_on_event(api, MixerEventCB); + afb_dynapi_on_init(api, MixerInitCB); + + int error = LoadStaticVerbs(mixer, CtrlApiVerbs); + if (error) goto OnErrorExit; + + return 0; + +OnErrorExit: + return -1; +} + CTLP_LUA2C(_mixer_new_, source, argsJ, responseJ) { - SoftMixerHandleT *mixer = calloc(1, sizeof (SoftMixerHandleT)); - json_object *backendJ = NULL, *frontendJ = NULL, *zonesJ = NULL, *streamsJ = NULL; + SoftMixerT *mixer = calloc(1, sizeof (SoftMixerT)); int error; - assert(source->api); + mixer->max.loops = SMIXER_DEFLT_RAMPS; + mixer->max.sinks = SMIXER_DEFLT_SINKS; + mixer->max.sources = SMIXER_DEFLT_SOURCES; + mixer->max.zones = SMIXER_DEFLT_ZONES; + mixer->max.streams = SMIXER_DEFLT_STREAMS; + mixer->max.ramps = SMIXER_DEFLT_RAMPS; 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 !}" + error = wrap_json_unpack(argsJ, "{ss,s?s,s?i,s?i,s?i,s?i,s?i,s?i !}" , "uid", &mixer->uid , "info", &mixer->info - , "backend", &backendJ - , "frontend", &frontendJ - , "zones", &zonesJ - , "streams", &streamsJ); + , "max_loop", &mixer->max.loops + , "max_sink", &mixer->max.sinks + , "max_source", &mixer->max.sources + , "max_zone", &mixer->max.zones + , "max_stream", &mixer->max.streams + , "max_ramp", &mixer->max.ramps + ); if (error) { - AFB_ApiNotice(source->api, "_mixer_new_ missing 'uid|backend|frontend|zones|streams' mixer=%s", json_object_get_string(argsJ)); + AFB_ApiNotice(source->api, "_mixer_new_ missing 'uid|max_loop|max_sink|max_source|max_zone|max_stream|max_ramp' error=%s mixer=%s", wrap_json_get_error_string(error),json_object_get_string(argsJ)); goto OnErrorExit; } @@ -210,34 +295,18 @@ CTLP_LUA2C(_mixer_new_, source, argsJ, responseJ) { mixer->uid = strdup(mixer->uid); if (mixer->info)mixer->info = strdup(mixer->info); + mixer->loops = calloc(mixer->max.loops+1, sizeof (AlsaSndLoopT)); + mixer->sinks = calloc(mixer->max.sinks+1, sizeof (AlsaSndPcmT)); + mixer->sources = calloc(mixer->max.sources+1, sizeof (AlsaSndPcmT)); + mixer->zones = calloc(mixer->max.zones+1, sizeof (AlsaSndZoneT)); + mixer->streams = calloc(mixer->max.streams+1, sizeof (AlsaStreamAudioT)); + mixer->ramps = calloc(mixer->max.ramps+1, sizeof (AlsaVolRampT)); + // create mixer verb within API. - error = afb_dynapi_add_verb(source->api, mixer->uid, mixer->info, MixerApiVerbCB, mixer, NULL, 0); + error = afb_dynapi_new_api(source->api, mixer->uid, mixer->info, !MAINLOOP_CONCURENCY, MixerApiCB, mixer); if (error) { AFB_ApiError(source->api, "_mixer_new_ mixer=%s fail to Registry API verb", mixer->uid); - return -1; - } - - // make sure sub command get access to mixer handle - source->context = mixer; - - 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 = LoopStreams(source, streamsJ, responseJ); - if (error) goto OnErrorExit; + goto OnErrorExit; } return 0; diff --git a/plugins/alsa/alsa-api-pcm.c b/plugins/alsa/alsa-api-pcm.c new file mode 100644 index 0000000..228b7a7 --- /dev/null +++ b/plugins/alsa/alsa-api-pcm.c @@ -0,0 +1,304 @@ +/* + * 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 + +// move from vol % to absolute value +#define CONVERT_RANGE(val, min, max) ceil((val) * ((max) - (min)) * 0.01 + (min)) +#define CONVERT_VOLUME(val, min, max) (int) CONVERT_RANGE ((double)val, (double)min, (double)max) + + +STATIC AlsaPcmChannelT *ProcessOneChannel(SoftMixerT *mixer, const char *uid, json_object *argsJ) { + AlsaPcmChannelT *channel = calloc(1, sizeof (AlsaPcmChannelT)); + int error = wrap_json_unpack(argsJ, "{ss,si !}", "uid", &channel->uid, "port", &channel->port); + if (error) goto OnErrorExit; + + channel->uid = strdup(channel->uid); + return channel; + +OnErrorExit: + AFB_ApiError(mixer->api, "ProcessOneChannel: sndcard=%s channel: missing (uid||port) error=%s json=%s", uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); + free(channel); + return NULL; +} + +STATIC int ProcessOneControl(SoftMixerT *mixer, AlsaSndPcmT* pcm, json_object *argsJ, AlsaSndControlT *control) { + snd_ctl_elem_id_t* elemId = NULL; + snd_ctl_elem_info_t *elemInfo; + int numid = 0; + long value = ALSA_DEFAULT_PCM_VOLUME; + const char *name; + + + int error = wrap_json_unpack(argsJ, "{s?i,s?s,s?i !}" + , "numid", &numid + , "name", &name + , "value", &value + ); + if (error || (!numid && !name)) { + AFB_ApiError(mixer->api, "ProcessOneControl: sndcard=%s channel: missing (numid|name|value) error=%s json=%s", pcm->uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); + goto OnErrorExit; + } + + if (numid > 0) { + elemId = AlsaCtlGetNumidElemId(mixer, pcm->sndcard, numid); + if (!elemId) { + AFB_ApiError(mixer->api, "ProcessOneControl sndard=%s fail to find control numid=%d", pcm->sndcard->cid.cardid, numid); + goto OnErrorExit; + } + + } else { + elemId = AlsaCtlGetNameElemId(mixer, pcm->sndcard, name); + if (!elemId) { + AFB_ApiError(mixer->api, "ProcessOneControl sndard=%s fail to find control name=%s", pcm->sndcard->cid.cardid, name); + goto OnErrorExit; + } + } + + snd_ctl_elem_info_alloca(&elemInfo); + snd_ctl_elem_info_set_id(elemInfo, elemId); + control->name = strdup(snd_ctl_elem_info_get_name(elemInfo)); + control->numid = snd_ctl_elem_info_get_numid(elemInfo); + + if (snd_ctl_elem_info(pcm->sndcard->ctl, elemInfo) < 0) { + AFB_ApiError(mixer->api, "ProcessOneControl: sndard=%s numid=%d name='%s' not loadable", pcm->sndcard->cid.cardid, control->numid, control->name); + goto OnErrorExit; + } + + if (!snd_ctl_elem_info_is_writable(elemInfo)) { + AFB_ApiError(mixer->api, "ProcessOneControl: sndard=%s numid=%d name='%s' not writable", pcm->sndcard->cid.cardid, control->numid, control->name); + goto OnErrorExit; + } + + control->count = snd_ctl_elem_info_get_count(elemInfo); + switch (snd_ctl_elem_info_get_type(elemInfo)) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + control->min = 0; + control->max = 1; + control->step = 0; + error = CtlElemIdSetLong(mixer, pcm->sndcard, elemId, value); + break; + + case SND_CTL_ELEM_TYPE_INTEGER: + case SND_CTL_ELEM_TYPE_INTEGER64: + control->min = snd_ctl_elem_info_get_min(elemInfo); + control->max = snd_ctl_elem_info_get_max(elemInfo); + control->step = snd_ctl_elem_info_get_step(elemInfo); + error = CtlElemIdSetLong(mixer, pcm->sndcard, elemId, (int) CONVERT_VOLUME(value, control->min, control->max)); + break; + + default: + AFB_ApiError(mixer->api, "ProcessOneControl: sndard=%s numid=%d name='%s' invalid/unsupported type=%d", pcm->sndcard->cid.cardid, control->numid, control->name, snd_ctl_elem_info_get_type(elemInfo)); + goto OnErrorExit; + } + + if (error) { + AFB_ApiError(mixer->api, "ProcessOneControl: sndard=%s numid=%d name='%s' not writable", pcm->sndcard->cid.cardid, control->numid, control->name); + goto OnErrorExit; + } + + free(elemId); + + return 0; + +OnErrorExit: + if (elemId)free(elemId); + return -1; +} + +PUBLIC AlsaPcmHwInfoT *ApiPcmSetParams(SoftMixerT *mixer, const char *uid, json_object *paramsJ) { + AlsaPcmHwInfoT *params = calloc(1, sizeof (AlsaPcmHwInfoT)); + const char *format = NULL, *access = NULL; + + // some default values + params->rate = ALSA_DEFAULT_PCM_RATE; + params->channels = 2; + params->sampleSize = 0; + + if (paramsJ) { + 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) { + AFB_ApiError(mixer->api, "ApiPcmSetParams: sndcard=%s invalid params=%s", uid, json_object_get_string(paramsJ)); + goto OnErrorExit; + } + } + + if (!format) { + params->format = SND_PCM_FORMAT_S16_LE; + params->formatS = "S16_LE"; + } else { + params->formatS = strdup(format); + 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(mixer->api, "ApiPcmSetParams:%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(mixer->api, "ApiPcmSetParams:%s(params) unsupported access 'RW_INTERLEAVED|MMAP_INTERLEAVED|MMAP_COMPLEX' access=%s", uid, access); + goto OnErrorExit; + } + + return params; + +OnErrorExit: + free(params); + return NULL; +} + +PUBLIC AlsaSndPcmT *ApiPcmAttachOne(SoftMixerT *mixer, const char *uid, snd_pcm_stream_t direction, json_object *argsJ) { + AlsaSndPcmT *pcm = calloc(1, sizeof (AlsaSndPcmT)); + json_object *sourceJ = NULL, *paramsJ = NULL, *sinkJ = NULL, *targetJ; + int error; + + pcm->sndcard = (AlsaSndCtlT*) calloc(1, sizeof (AlsaSndCtlT)); + error = wrap_json_unpack(argsJ, "{ss,s?s,s?s,s?i,s?i,s?o,s?o,s?o !}" + , "uid", &pcm->uid + , "path", &pcm->sndcard->cid.devpath + , "cardid", &pcm->sndcard->cid.cardid + , "device", &pcm->sndcard->cid.device + , "subdev", &pcm->sndcard->cid.subdev + , "sink", &sinkJ + , "source", &sourceJ + , "params", ¶msJ + ); + if (error) { + AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s missing 'uid|path|cardid|device|sink|source|params' error=%s args=%s", uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); + goto OnErrorExit; + } + + // try to open sound card control interface + pcm->sndcard->ctl = AlsaByPathOpenCtl(mixer, pcm->uid, pcm->sndcard); + if (!pcm->sndcard->ctl) { + AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s Fail to open sndcard uid=%s devpath=%s cardid=%s", uid, pcm->uid, pcm->sndcard->cid.devpath, pcm->sndcard->cid.cardid); + goto OnErrorExit; + } + + // check sndcard accepts params + pcm->sndcard->params = ApiPcmSetParams(mixer, pcm->uid, paramsJ); + if (!pcm->sndcard->params) { + AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s Fail to set params sndcard uid=%s params=%s", uid, pcm->uid, json_object_get_string(paramsJ)); + goto OnErrorExit; + } + + if (direction == SND_PCM_STREAM_PLAYBACK) { + if (!sinkJ) { + AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s SND_PCM_STREAM_PLAYBACK require sinks args=%s", uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + targetJ = sinkJ; + } + + if (direction == SND_PCM_STREAM_CAPTURE) { + if (!sourceJ) { + AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s SND_PCM_STREAM_CAPTURE require sources args=%s", uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + targetJ = sourceJ; + + // we may have to register SMIXER_SUBDS_CTLS per subdev (Fulup ToBeDone when sndcard get multiple device/subdev) + pcm->sndcard->registry = calloc(SMIXER_SUBDS_CTLS+1, sizeof (RegistryEntryPcmT)); + pcm->sndcard->rcount = SMIXER_SUBDS_CTLS; + } + + json_object *channelsJ = NULL, *controlsJ = NULL; + error = wrap_json_unpack(targetJ, "{so,s?o !}" + , "channels", &channelsJ + , "controls", &controlsJ + ); + if (error) { + AFB_ApiNotice(mixer->api, "ApiPcmAttachOne: hal=%s pcms missing channels|[controls] error=%s paybacks=%s", uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); + goto OnErrorExit; + } + if (channelsJ) { + switch (json_object_get_type(channelsJ)) { + + case json_type_object: + pcm->ccount = 1; + pcm->channels = calloc(2, sizeof (void*)); + pcm->channels[0] = ProcessOneChannel(mixer, pcm->uid, channelsJ); + if (!pcm->channels[0]) goto OnErrorExit; + break; + case json_type_array: + pcm->ccount = (int) json_object_array_length(channelsJ); + pcm->channels = calloc(pcm->ccount + 1, sizeof (void*)); + for (int idx = 0; idx < pcm->ccount; idx++) { + json_object *channelJ = json_object_array_get_idx(channelsJ, idx); + pcm->channels[idx] = ProcessOneChannel(mixer, pcm->uid, channelJ); + if (!pcm->channels[idx]) goto OnErrorExit; + } + break; + default: + AFB_ApiError(mixer->api, "ProcessPcmControls:%s invalid pcm=%s", pcm->uid, json_object_get_string(channelsJ)); + goto OnErrorExit; + } + } + + if (controlsJ) { + json_object *volJ = NULL, *muteJ = NULL; + error = wrap_json_unpack(controlsJ, "{s?o,s?o !}" + , "volume", &volJ + , "mute", &muteJ + ); + if (error) { + AFB_ApiNotice(mixer->api, "ProcessPcmControls: source missing [volume]|[mute] error=%s control=%s", wrap_json_get_error_string(error), json_object_get_string(controlsJ)); + goto OnErrorExit; + } + + if (volJ) error += ProcessOneControl(mixer, pcm, volJ, &pcm->volume); + if (muteJ) error += ProcessOneControl(mixer, pcm, muteJ, &pcm->mute); + if (error) goto OnErrorExit; + } + + // free useless resource and secure others + pcm->uid = strdup(pcm->uid); + + return pcm; + +OnErrorExit: + free(pcm); + return NULL; +} + diff --git a/plugins/alsa/alsa-api-ramp.c b/plugins/alsa/alsa-api-ramp.c new file mode 100644 index 0000000..002aec0 --- /dev/null +++ b/plugins/alsa/alsa-api-ramp.c @@ -0,0 +1,114 @@ +/* + * 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 + +PUBLIC AlsaVolRampT *ApiRampGetByUid(SoftMixerT *mixer, const char *uid) { + AlsaVolRampT *ramp = NULL; + + // Loop on every Register zone pcm and extract (cardid) from (uid) + for (int idx = 0; mixer->ramps[idx]->uid != NULL; idx++) { + if (!strcasecmp(mixer->ramps[idx]->uid, uid)) { + ramp = mixer->ramps[idx]; + return ramp; + } + } + return NULL; +} + +STATIC AlsaVolRampT *AttachOneRamp(SoftMixerT *mixer, const char *uid, json_object *rampJ) { + const char*rampUid; + AlsaVolRampT *ramp = calloc(1, sizeof (AlsaVolRampT)); + + int error = wrap_json_unpack(rampJ, "{ss,si,si,si !}" + , "uid", &rampUid + , "delay", &ramp->delay + , "up", &ramp->stepUp + , "down", &ramp->stepDown + ); + if (error) { + AFB_ApiError(mixer->api, "AttachOneRamp mixer=%s hal=%s error=%s json=%s", mixer->uid, uid, wrap_json_get_error_string(error), json_object_get_string(rampJ)); + goto OnErrorExit; + } + + ramp->delay = ramp->delay * 100; // move from ms to us + ramp->uid = strdup(rampUid); + return ramp; + +OnErrorExit: + free(ramp); + return NULL; +} + +PUBLIC int ApiRampAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object *argsJ) { + int index; + + for (index = 0; index < mixer->max.ramps; index++) { + if (!mixer->ramps[index]) break; + } + + if (index == mixer->max.ramps) { + AFB_ReqFailF(request, "too-small", "mixer=%s hal=%s max ramp=%d argsJ= %s", mixer->uid, uid, mixer->max.ramps, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + switch (json_object_get_type(argsJ)) { + long count; + + case json_type_object: + mixer->ramps[index] = AttachOneRamp(mixer, uid, argsJ); + if (!mixer->ramps[index]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s hal=%s invalid ramp= %s", mixer->uid, uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + break; + + case json_type_array: + count = json_object_array_length(argsJ); + if (count > (mixer->max.ramps - count)) { + AFB_ReqFailF(request, "too-small", "mixer=%s hal=%s max ramp=%d argsJ= %s", mixer->uid, uid, mixer->max.ramps, json_object_get_string(argsJ)); + goto OnErrorExit; + + } + + for (int idx = 0; idx < count; idx++) { + json_object *streamAudioJ = json_object_array_get_idx(argsJ, idx); + mixer->ramps[index + idx] = AttachOneRamp(mixer, uid, streamAudioJ); + if (!mixer->ramps[index + idx]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s hal=%s invalid ramp= %s", mixer->uid, uid, json_object_get_string(streamAudioJ)); + goto OnErrorExit; + } + } + break; + default: + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s hal=%s ramps invalid argsJ= %s", mixer->uid, uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + return 0; + +OnErrorExit: + return -1; +} diff --git a/plugins/alsa/alsa-api-sink.c b/plugins/alsa/alsa-api-sink.c new file mode 100644 index 0000000..ab32276 --- /dev/null +++ b/plugins/alsa/alsa-api-sink.c @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author Fulup Ar Foll + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#define _GNU_SOURCE // needed for vasprintf + +#include "alsa-softmixer.h" + +PUBLIC AlsaPcmHwInfoT *ApiSinkGetParamsByZone(SoftMixerT *mixer, const char *target) { + + AlsaSndZoneT *zone = ApiZoneGetByUid(mixer, target); + if (!zone || !zone->sinks) return NULL; + + // use 1st channel to find attached sound card. + const char *channel = zone->sinks[0]->uid; + + // search for channel uid into mixer sinks + for (int idx = 0; mixer->sinks[idx]; idx++) { + for (int jdx = 0; jdx < mixer->sinks[idx]->ccount; jdx++) { + if (mixer->sinks[idx]->channels[jdx]->uid && !strcasecmp(channel, mixer->sinks[idx]->channels[jdx]->uid)) { + return mixer->sinks[idx]->sndcard->params; + } + } + } + return NULL; +} + +PUBLIC int ApiSinkAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ) { + + int index; + for (index = 0; index < mixer->max.sinks; index++) { + if (!mixer->sinks[index]) break; + } + + if (index == mixer->max.sinks) { + AFB_ReqFailF(request, "too-small", "mixer=%s max sink=%d argsJ= %s", mixer->uid, mixer->max.sinks, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + switch (json_object_get_type(argsJ)) { + long count; + char *dmixUid; + AlsaPcmCtlT* dmixConfig; + + case json_type_object: + mixer->sinks[index] = ApiPcmAttachOne(mixer, uid, SND_PCM_STREAM_PLAYBACK, argsJ); + if (!mixer->sinks[index]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s invalid sink= %s", mixer->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + // move from hardware to DMIX attach to sndcard + (void) asprintf(&dmixUid, "dmix-%s", mixer->sinks[index]->uid); + + dmixConfig = AlsaCreateDmix(mixer, dmixUid, mixer->sinks[index], 0); + if (!dmixConfig) { + AFB_ReqFailF(request, "internal-error", "mixer=%s sink=%s fail to create DMIX config", mixer->uid, mixer->sinks[index]->uid); + goto OnErrorExit; + } + + break; + + case json_type_array: + count = json_object_array_length(argsJ); + if (count > (mixer->max.sinks - count)) { + AFB_ReqFailF(request, "too-small", "mixer=%s max sink=%d argsJ= %s", mixer->uid, mixer->max.sinks, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + for (int idx = 0; idx < count; idx++) { + json_object *sinkJ = json_object_array_get_idx(argsJ, idx); + mixer->sinks[index + idx] = ApiPcmAttachOne(mixer, uid, SND_PCM_STREAM_PLAYBACK, sinkJ); + if (!mixer->sinks[index + idx]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s invalid sink= %s", mixer->uid, json_object_get_string(sinkJ)); + goto OnErrorExit; + } + + // move from hardware to DMIX attach to sndcard + (void) asprintf(&dmixUid, "dmix-%s", mixer->sinks[index]->uid); + + dmixConfig = AlsaCreateDmix(mixer, dmixUid, mixer->sinks[index], 0); + if (!dmixConfig) { + AFB_ReqFailF(request, "internal-error", "mixer=%s sink=%s fail to create DMIX config", mixer->uid, mixer->sinks[index]->uid); + goto OnErrorExit; + } + } + break; + default: + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s sinks invalid argsJ= %s", mixer->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-source.c b/plugins/alsa/alsa-api-source.c new file mode 100644 index 0000000..6315a14 --- /dev/null +++ b/plugins/alsa/alsa-api-source.c @@ -0,0 +1,84 @@ +/* + * 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 + +PUBLIC AlsaSndCtlT *ApiSourceFindSubdev(SoftMixerT *mixer, const char *target) { + + // search for subdev into every registered source + for (int idx = 0; mixer->sources[idx]; idx++) { + if (mixer->sources[idx]->uid && !strcasecmp(mixer->sources[idx]->uid, target)) { + return mixer->sources[idx]->sndcard; + } + } + return NULL; +} + +PUBLIC int ApiSourceAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ) { + + int index; + for (index = 0; index < mixer->max.sources; index++) { + if (!mixer->sources[index]) break; + } + + if (index == mixer->max.sources) { + AFB_ReqFailF(request, "too-small", "mixer=%s max source=%d argsJ= %s", mixer->uid, mixer->max.sources, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + switch (json_object_get_type(argsJ)) { + long count; + + case json_type_object: + mixer->sources[index] = ApiPcmAttachOne(mixer, uid, SND_PCM_STREAM_CAPTURE, argsJ); + if (!mixer->sources[index]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s invalid source= %s", mixer->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + break; + + case json_type_array: + count = json_object_array_length(argsJ); + if (count > (mixer->max.sources - count)) { + AFB_ReqFailF(request, "too-small", "mixer=%s max source=%d argsJ= %s", mixer->uid, mixer->max.sources, json_object_get_string(argsJ)); + goto OnErrorExit; + + } + + for (int idx = 0; idx < count; idx++) { + json_object *sourceJ = json_object_array_get_idx(argsJ, idx); + mixer->sources[index + idx] = ApiPcmAttachOne(mixer, uid, SND_PCM_STREAM_CAPTURE, sourceJ); + if (!mixer->sources[index + idx]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s invalid source= %s", mixer->uid, json_object_get_string(sourceJ)); + goto OnErrorExit; + } + } + break; + default: + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s sources invalid argsJ= %s", mixer->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-streams.c b/plugins/alsa/alsa-api-streams.c index 3923048..f531ed5 100644 --- a/plugins/alsa/alsa-api-streams.c +++ b/plugins/alsa/alsa-api-streams.c @@ -30,37 +30,22 @@ extern Lua2cWrapperT Lua2cWrap; typedef struct { - const char* verb; - AlsaLoopStreamT *stream; - SoftMixerHandleT *mixer; + AlsaStreamAudioT *stream; + SoftMixerT *mixer; AlsaVolRampT *ramp; + AlsaSndCtlT *sndcard; + snd_pcm_t *pcm; } apiHandleT; -STATIC AlsaVolRampT* RampGetByUid(CtlSourceT *source, AlsaVolRampT *ramps, const char *uid) { - AlsaVolRampT *ramp = NULL; - - // Loop on every Registryed zone pcm and extract (cardid) from (uid) - for (int idx = 0; ramps[idx].uid != NULL; idx++) { - if (!strcasecmp(ramps[idx].uid, uid)) { - ramp = &ramps[idx]; - return ramp; - } - } - return NULL; -} - STATIC void StreamApiVerbCB(AFB_ReqT request) { + apiHandleT *handle = (apiHandleT*) afb_request_get_vcbdata(request); int error, doClose = 0, doQuiet = 0, doToggle = 0, doMute = -1; long mute, volume; json_object *responseJ, *volumeJ = NULL, *rampJ = NULL, *argsJ = afb_request_json(request); - apiHandleT *handle = (apiHandleT*) afb_request_get_vcbdata(request); - snd_ctl_t *ctlDev = NULL; - CtlSourceT *source = alloca(sizeof (CtlSourceT)); - source->uid = handle->verb; - source->api = request->dynapi; - source->request = NULL; - source->context = NULL; + SoftMixerT *mixer = handle->mixer; + AlsaSndCtlT *sndcard = handle->sndcard; + assert(mixer && sndcard); error = wrap_json_unpack(argsJ, "{s?b s?b,s?b,s?b,s?o,s?o !}" , "quiet", &doQuiet @@ -71,31 +56,25 @@ STATIC void StreamApiVerbCB(AFB_ReqT request) { , "ramp", &rampJ ); if (error) { - AFB_ReqFailF(request, "StreamApiVerbCB", "Missing 'close|mute|volume|quiet' args=%s", json_object_get_string(argsJ)); - goto OnErrorExit; - } - - ctlDev = AlsaCtlOpenCtl(source, handle->mixer->frontend->cardid); - if (!ctlDev) { - AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to open sndcard=%s", handle->mixer->frontend->cardid); + AFB_ReqFailF(request, "syntax-error", "Missing 'close|mute|volume|quiet' args=%s", json_object_get_string(argsJ)); goto OnErrorExit; } if (doClose) { - AFB_ReqFailF(request, "StreamApiVerbCB", "(Fulup) Close action still to be done mixer=%s", json_object_get_string(argsJ)); + AFB_ReqFailF(request, "internal-error", "(Fulup) Close action still to be done mixer=%s stream=%s", mixer->uid, handle->stream->uid); goto OnErrorExit; } if (doToggle) { - error += AlsaCtlNumidGetLong(source, ctlDev, handle->stream->mute, &mute); - error += AlsaCtlNumidSetLong(source, ctlDev, handle->stream->mute, !mute); + error += AlsaCtlNumidGetLong(mixer, sndcard, handle->stream->mute, &mute); + error += AlsaCtlNumidSetLong(mixer, sndcard, handle->stream->mute, !mute); } if (volumeJ) { long curvol, newvol; const char*volString; - error = AlsaCtlNumidGetLong(source, ctlDev, handle->stream->volume, &curvol); + error = AlsaCtlNumidGetLong(mixer, handle->sndcard, handle->stream->volume, &curvol); if (error) { AFB_ReqFailF(request, "invalid-numid", "Fail to set volume numid=%d value=%ld", handle->stream->volume, volume); goto OnErrorExit; @@ -129,15 +108,15 @@ STATIC void StreamApiVerbCB(AFB_ReqT request) { } - error = AlsaCtlNumidSetLong(source, ctlDev, handle->stream->volume, newvol); + error = AlsaCtlNumidSetLong(mixer, handle->sndcard, handle->stream->volume, newvol); if (error) { AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volume numid=%d value=%ld", handle->stream->volume, newvol); goto OnErrorExit; } } - if (rampJ) { - error = AlsaVolRampApply(source, handle->mixer->frontend, handle->stream, handle->ramp, rampJ); + if (rampJ) { + error = AlsaVolRampApply(mixer, handle->sndcard, handle->stream, rampJ); if (error) { AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volram numid=%d value=%s", handle->stream->volume, json_object_get_string(rampJ)); goto OnErrorExit; @@ -145,7 +124,7 @@ STATIC void StreamApiVerbCB(AFB_ReqT request) { } if (doMute != -1) { - error = AlsaCtlNumidSetLong(source, ctlDev, handle->stream->mute, !mute); + error = AlsaCtlNumidSetLong(mixer, handle->sndcard, handle->stream->mute, !mute); if (error) { AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volume numid=%d value=%d", handle->stream->volume, !mute); goto OnErrorExit; @@ -155,8 +134,8 @@ STATIC void StreamApiVerbCB(AFB_ReqT request) { // if not in quiet mode return effective selected control values if (doQuiet) responseJ = NULL; else { - error += AlsaCtlNumidGetLong(source, ctlDev, handle->stream->volume, &volume); - error += AlsaCtlNumidGetLong(source, ctlDev, handle->stream->mute, &mute); + error += AlsaCtlNumidGetLong(mixer, handle->sndcard, handle->stream->volume, &volume); + error += AlsaCtlNumidGetLong(mixer, handle->sndcard, handle->stream->mute, &mute); if (error) { AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to get stream numids volume=%ld mute=%ld", volume, mute); goto OnErrorExit; @@ -164,17 +143,188 @@ STATIC void StreamApiVerbCB(AFB_ReqT request) { wrap_json_pack(&responseJ, "{si,sb}", "volume", volume, "mute", !mute); } - snd_ctl_close(ctlDev); - AFB_ReqSucess(request, responseJ, handle->verb); + AFB_ReqSucess(request, responseJ, NULL); return; OnErrorExit: - if (ctlDev) snd_ctl_close(ctlDev); return; +} + +PUBLIC json_object *CreateOneStream(SoftMixerT *mixer, AlsaStreamAudioT *stream) { + int error; + long value; + AlsaSndLoopT *loop=NULL; + AlsaPcmCtlT *streamPcm; + AlsaSndCtlT *captureCard; + AlsaDevInfoT *captureDev = alloca(sizeof (AlsaDevInfoT)); + AlsaLoopSubdevT *loopDev; + char * captureName; + + loopDev = ApiLoopFindSubdev(mixer, stream->uid, stream->source, &loop); + if (loopDev) { + // create a valid PCM reference and try to open it. + captureDev->devpath = NULL; + captureDev->cardid = NULL; + captureDev->cardidx = loop->sndcard->cid.cardidx; + captureDev->device = loop->capture; + captureDev->subdev = loopDev->index; + captureCard = loop->sndcard; + } else { + // if capture UID is not present in loop search on sources + AlsaSndCtlT *sourceDev = ApiSourceFindSubdev(mixer, stream->source); + if (sourceDev) { + captureDev->devpath = NULL; + captureDev->cardid = NULL; + captureDev->cardidx = sourceDev->cid.cardidx; + captureDev->device = sourceDev->cid.device; + captureDev->subdev = sourceDev->cid.subdev; + captureCard = sourceDev; + } else { + AFB_ApiError(mixer->api, "CreateOneStream: mixer=%s stream=%s not found in loops/sources", mixer->uid, stream->uid); + goto OnErrorExit; + } + } + // check PCM is valid and get its full name + AlsaPcmCtlT *capturePcm = AlsaByPathOpenPcm(mixer, captureDev, SND_PCM_STREAM_CAPTURE); + if (!capturePcm) goto OnErrorExit; + + // Registry capturePcm PCM for active/pause event + if (loopDev && loopDev->numid) { + error = AlsaCtlRegister(mixer, captureCard, capturePcm, FONTEND_NUMID_RUN, loopDev->numid); + if (error) goto OnErrorExit; + } + + AlsaSndZoneT *zone = ApiZoneGetByUid(mixer, stream->sink); + if (!zone) { + AFB_ApiError(mixer->api, "CreateOneStream: mixer=%s stream=%s fail to find sink zone='%s'", mixer->uid, stream->uid, stream->sink); + goto OnErrorExit; + } + + // retrieve channel count from route and push it to stream + stream->params->channels = zone->ccount; + + // create mute control and Registry it as pause/resume ctl) + char *runName; + (void) asprintf(&runName, "pause-%s", stream->uid); + int pauseNumid = AlsaCtlCreateControl(mixer, captureCard, runName, 1, 0, 1, 1, stream->mute); + if (pauseNumid <= 0) goto OnErrorExit; + + // Registry stop/play as a pause/resume control + error = AlsaCtlRegister(mixer, captureCard, capturePcm, FONTEND_NUMID_PAUSE, pauseNumid); + if (error) goto OnErrorExit; + + + char *volName; + (void) asprintf(&volName, "vol-%s", stream->uid); + + // create stream and delay pcm opening until vol control is created + streamPcm = AlsaCreateSoftvol(mixer, stream, zone, captureCard, volName, VOL_CONTROL_MAX, 0); + if (!streamPcm) { + AFB_ApiError(mixer->api, "CreateOneStream: mixer=%s stream=%s fail to create stream", mixer->uid, stream->uid); + goto OnErrorExit; + } + + // create volume control before softvol pcm is opened + int volNumid = AlsaCtlCreateControl(mixer, captureCard, volName, stream->params->channels, VOL_CONTROL_MIN, VOL_CONTROL_MAX, VOL_CONTROL_STEP, stream->volume); + if (volNumid <= 0) goto OnErrorExit; + + if ((zone->params->rate != stream->params->rate) || (zone->params->format != stream->params->format)) { + char *rateName; + (void) asprintf(&rateName, "rate-%s", stream->uid); + streamPcm = AlsaCreateRate(mixer, rateName, streamPcm, zone->params, 0); + if (!streamPcm) { + AFB_ApiError(mixer->api, "StreamsAttach: mixer=%s stream=%s fail to create rate converter", mixer->uid, stream->uid); + goto OnErrorExit; + } + captureName=rateName; + } else { + captureName= (char*)streamPcm->cid.cardid; + } + + // everything is not ready to open playback pcm + error = snd_pcm_open(&streamPcm->handle, captureName, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (error) { + AFB_ApiError(mixer->api, "CreateOneStream: mixer=%s stream=%s fail to open capturePcm=%s error=%s", mixer->uid, stream->uid, streamPcm->cid.cardid, snd_strerror(error)); + goto OnErrorExit; + } + + // start stream pcm copy (at this both capturePcm & sink pcm should be open, we use output params to configure both in+outPCM) + error = AlsaPcmCopy(mixer, stream, capturePcm, streamPcm, stream->params); + if (error) goto OnErrorExit; + + error = AlsaCtlRegister(mixer, captureCard, capturePcm, FONTEND_NUMID_IGNORE, volNumid); + if (error) goto OnErrorExit; + + // when using loopdev check if subdev is active or not to prevent thread from reading empty packet + if (loopDev && loopDev->numid) { + // retrieve active/pause control and set PCM status accordingly + error = AlsaCtlNumidGetLong(mixer, captureCard, loopDev->numid, &value); + if (error) goto OnErrorExit; + + // toggle pause/resume (should be done after pcm_start) + if ((error = snd_pcm_pause(capturePcm->handle, !value)) < 0) { + AFB_ApiWarning(mixer->api, "CreateOneStream: mixer=%s [capturePcm=%s] fail to pause error=%s", mixer->uid, captureDev->cardid, snd_strerror(error)); + } + } + + // prepare response for application + json_object *paramsJ, *streamJ; + char *appCardId = NULL; + + // return alsa URI only when loopback is used + if (loop) { + (void) asprintf(&appCardId, "hw:%d,%d,%d", captureDev->cardidx, loop->playback, capturePcm->cid.subdev); + } else { + appCardId=""; + } + + error += wrap_json_pack(¶msJ, "{si si si si}" + , "rate", stream->params->rate + , "channels", stream->params->channels + , "format", stream->params->format + , "access", stream->params->access + ); + error += wrap_json_pack(&streamJ, "{ss ss si si so}", "uid" + , stream->uid, "alsa" + , appCardId + , "volid", volNumid + , "runid", pauseNumid + , "params", paramsJ + ); + if (error) { + AFB_ApiError(mixer->api, "CreateOneStream: mixer=%s stream=%s fail to prepare response", mixer->uid, stream->uid); + goto OnErrorExit; + } + + // create a dedicated verb for this stream + apiHandleT *apiHandle = calloc(1, sizeof (apiHandleT)); + + apiHandle->mixer = mixer; + apiHandle->stream = stream; + apiHandle->sndcard =captureCard; + apiHandle->pcm = capturePcm->handle; + + error = afb_dynapi_add_verb(mixer->api, stream->uid, stream->info, StreamApiVerbCB, apiHandle, NULL, 0); + if (error) { + AFB_ApiError(mixer->api, "CreateOneStream mixer=%s fail to Register API verb stream=%s", mixer->uid, stream->uid); + goto OnErrorExit; + } + + // Debug Alsa Config + //AlsaDumpElemConfig (source, "\n\nAlsa_Config\n------------\n", "pcm"); + //AlsaDumpPcmInfo(source, "\n\nPcm_config\n-----------\n", streamPcm->handle); + + AFB_ApiNotice(mixer->api, "CreateOneStream: mixer=%s stream=%s OK reponse=%s\n", mixer->uid, stream->uid, json_object_get_string(streamJ)); + + return streamJ; + +OnErrorExit: + return NULL; } -STATIC int ProcessOneStream(CtlSourceT *source, json_object *streamJ, AlsaLoopStreamT *stream) { +STATIC AlsaStreamAudioT * AttachOneStream(SoftMixerT *mixer, const char *uid, json_object *streamJ, json_object **responseJ) { + AlsaStreamAudioT *stream = calloc(1, sizeof (AlsaStreamAudioT)); int error; json_object *paramsJ = NULL; @@ -183,242 +333,93 @@ STATIC int ProcessOneStream(CtlSourceT *source, json_object *streamJ, AlsaLoopSt stream->mute = 0; stream->info = NULL; - error = wrap_json_unpack(streamJ, "{ss,s?s,ss,s?i,s?b,s?o,s?s !}" + error = wrap_json_unpack(streamJ, "{ss,s?s,ss,s?s,s?i,s?b,s?o,s?s !}" , "uid", &stream->uid , "info", &stream->info - , "zone", &stream->zone + , "zone", &stream->sink + , "source", &stream->source , "volume", &stream->volume , "mute", stream->mute , "params", ¶msJ , "ramp", &stream->ramp ); if (error) { - AFB_ApiNotice(source->api, "ProcessOneStream missing 'uid|[info]|zone|[volume]|[mute]|[params]' stream=%s", json_object_get_string(streamJ)); + AFB_ApiNotice(mixer->api, "ProcessOneStream hal=%s missing 'uid|[info]|zone|source||[volume]|[mute]|[params]' error=%s stream=%s", uid, wrap_json_get_error_string(error), 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)); + stream->params = ApiPcmSetParams(mixer, stream->uid, paramsJ); + if (!stream->params) { + AFB_ApiError(mixer->api, "ProcessOneSndCard: hal=%s stream=%s invalid params=%s", uid, 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); + if (stream->sink)stream->sink = strdup(stream->sink); + if (stream->source)stream->source = strdup(stream->source); - return 0; + // implement stream PCM with corresponding thread and controls + *responseJ = CreateOneStream(mixer, stream); + + return stream; OnErrorExit: - return -1; + return NULL; } -PUBLIC int LoopStreams(CtlSourceT *source, json_object *argsJ, json_object **responseJ) { - SoftMixerHandleT *mixer = (SoftMixerHandleT*) source->context; - AlsaLoopStreamT *loopStream; - int error; - long value; - size_t count; +PUBLIC int ApiStreamAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ, json_object **responseJ) { + + if (!mixer->loops) { + AFB_ApiError(mixer->api, "StreamsAttach: mixer=%s No Loop found [should Registry snd_loop first]", mixer->uid); + goto OnErrorExit; + } - assert(mixer); + int index; + for (index = 0; index < mixer->max.streams; index++) { + if (!mixer->streams[index]) break; + } - // assert static/global softmixer handle get requited info - AlsaSndLoopT *ctlLoop = mixer->frontend; - if (!ctlLoop) { - AFB_ApiError(source->api, "LoopStreams: mixer=%s No Loop found [should Registry snd_loop first]", mixer->uid); + if (index == mixer->max.streams) { + AFB_ReqFailF(request, "too-small", "mixer=%s max stream=%d argsJ= %s", mixer->uid, mixer->max.streams, json_object_get_string(argsJ)); goto OnErrorExit; } switch (json_object_get_type(argsJ)) { + long count; + case json_type_object: - count = 1; - loopStream = calloc(count + 1, sizeof (AlsaLoopStreamT)); - error = ProcessOneStream(source, argsJ, &loopStream[0]); - if (error) { - AFB_ApiError(source->api, "LoopStreams: mixer=%s invalid stream= %s", mixer->uid, json_object_get_string(argsJ)); + mixer->streams[index] = AttachOneStream(mixer, uid, argsJ, responseJ); + if (!mixer->streams[index]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s invalid stream= %s", mixer->uid, json_object_get_string(argsJ)); goto OnErrorExit; } break; case json_type_array: + *responseJ = json_object_new_array(); + count = json_object_array_length(argsJ); - loopStream = calloc(count + 1, sizeof (AlsaLoopStreamT)); + if (count > (mixer->max.streams - count)) { + AFB_ReqFailF(request, "too-small", "mixer=%s max stream=%d argsJ= %s", mixer->uid, mixer->max.streams, json_object_get_string(argsJ)); + goto OnErrorExit; + } + for (int idx = 0; idx < count; idx++) { - json_object *loopStreamJ = json_object_array_get_idx(argsJ, idx); - error = ProcessOneStream(source, loopStreamJ, &loopStream[idx]); - if (error) { - AFB_ApiError(source->api, "loopstreams: mixer=%s invalid stream= %s", mixer->uid, json_object_get_string(loopStreamJ)); + json_object *streamJ = json_object_array_get_idx(argsJ, idx); + mixer->streams[index + idx] = AttachOneStream(mixer, uid, streamJ, &streamJ); + if (!mixer->streams[index + idx]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s invalid stream= %s", mixer->uid, json_object_get_string(streamJ)); goto OnErrorExit; } + (void) json_object_array_add(*responseJ, streamJ); } break; default: - AFB_ApiError(source->api, "LoopStreams: mixer=%s invalid argsJ= %s", mixer->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; loopStream[idx].uid != NULL; idx++) { - json_object *streamJ, *paramsJ; - - // Search for a free loop capture device - AFB_ApiNotice(source->api, "LoopStreams: mixer=%s stream=%s Start", mixer->uid, (char*) loopStream[idx].uid); - ctlLoop->scount--; - if (ctlLoop->scount < 0) { - AFB_ApiError(source->api, "LoopStreams: mixer=%s stream=%s no more subdev avaliable in loopback=%s", mixer->uid, loopStream[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; - - // Registry capture PCM for active/pause event - if (captureDev->numid) { - error = AlsaCtlRegister(source, mixer, captureDev, FONTEND_NUMID_RUN, 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, "LoopStreams: mixer=%s [pcm=%s] fail attache sndcard", mixer->uid, captureDev->cardid); - goto OnErrorExit; - } - - // create mute control and Registry it as pause/resume ctl) - char runName[ALSA_CARDID_MAX_LEN]; - snprintf(runName, sizeof (runName), "run-%s", loopStream[idx].uid); - - // create a single boolean value control for pause/resume - int pauseNumid = AlsaCtlCreateControl(source, ctlDev, playbackDev, runName, 1, 0, 1, 1, loopStream[idx].mute); - if (pauseNumid <= 0) goto OnErrorExit; - - // Registry mute/unmute as a pause/resume control - error = AlsaCtlRegister(source, mixer, captureDev, FONTEND_NUMID_PAUSE, pauseNumid); - 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", loopStream[idx].uid); - AlsaPcmInfoT *streamPcm = AlsaCreateSoftvol(source, &loopStream[idx], captureDev, volName, VOL_CONTROL_MAX, 0); - if (!streamPcm) { - AFB_ApiError(source->api, "LoopStreams: mixer=%s%s(pcm) fail to create stream", mixer->uid, loopStream[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, loopStream[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", loopStream[idx].uid); - // AlsaPcmInfoT *ratePcm= AlsaCreateRate(source, rateName, streamPcm, 1); - // if (!ratePcm) { - // AFB_ApiError(source->api, "LoopStreams: mixer=%s%s(pcm) fail to create rate converter", loopStream[idx].uid); - // goto OnErrorExit; - // } - - // everything is not ready to open capture pcm - error = snd_pcm_open(&streamPcm->handle, loopStream[idx].uid, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); - if (error) { - AFB_ApiError(source->api, "LoopStreams: mixer=%s%s(pcm) fail to open capture", mixer->uid, loopStream[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, we use output params to configure both in+outPCM) - error = AlsaPcmCopy(source, &loopStream[idx], captureDev, streamPcm, &streamPcm->params); - if (error) goto OnErrorExit; - - error = AlsaCtlRegister(source, mixer, captureDev, FONTEND_NUMID_IGNORE, volNumid); - 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_ApiWarning(source->api, "LoopStreams: mixer=%s [capture=%s] fail to pause error=%s", mixer->uid, captureDev->cardid, snd_strerror(error)); - } - - // 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", pauseNumid, "params", paramsJ); - error += json_object_array_add(*responseJ, streamJ); - if (error) { - AFB_ApiError(source->api, "LoopStreams: mixer=%s stream=%s fail to prepare response", mixer->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", mixer->uid, loopStream[idx].uid); - if (error == sizeof (apiVerb)) { - AFB_ApiError(source->api, "LoopStreams mixer=%s fail to Registry Stream API too long %s/%s", mixer->uid, mixer->uid, loopStream[idx].uid); - goto OnErrorExit; - } - - // if set get stream attached volramp - if (loopStream->ramp) { - apiHandle->ramp = RampGetByUid(source, ctlLoop->ramps, loopStream->ramp); - if (!apiHandle->ramp) { - AFB_ApiError(source->api, "LoopStreams: mixer=%s%s(pcm) fail to find ramp=%s", mixer->uid, loopStream[idx].uid, loopStream->ramp); - goto OnErrorExit; - } - } - - apiHandle->mixer = mixer; - apiHandle->stream= &loopStream[idx]; - apiHandle->verb = strdup(apiVerb); - error = afb_dynapi_add_verb(source->api, apiHandle->verb, loopStream[idx].info, StreamApiVerbCB, apiHandle, NULL, 0); - if (error) { - AFB_ApiError(source->api, "LoopStreams mixer=%s fail to Registry API verb=%s", mixer->uid, apiHandle->verb); + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s streams invalid argsJ= %s", mixer->uid, json_object_get_string(argsJ)); goto OnErrorExit; - } - - // free temporary resources - snd_ctl_close(ctlDev); - loopStream[idx].volume = volNumid; - loopStream[idx].mute = pauseNumid; - - // Debug Alsa Config - //AlsaDumpElemConfig (source, "\n\nAlsa_Config\n------------\n", "pcm"); - //AlsaDumpPcmInfo(source, "\n\nPcm_config\n-----------\n", streamPcm->handle); - - AFB_ApiNotice(source->api, "LoopStreams: mixer=%s stream=%s OK reponse=%s\n", mixer->uid, streamPcm->uid, json_object_get_string(streamJ)); } - // save handle for further use - mixer->streams = loopStream; return 0; OnErrorExit: diff --git a/plugins/alsa/alsa-api-zones.c b/plugins/alsa/alsa-api-zones.c index 8f383df..32af733 100644 --- a/plugins/alsa/alsa-api-zones.c +++ b/plugins/alsa/alsa-api-zones.c @@ -16,7 +16,7 @@ * */ -#define _GNU_SOURCE // needed for vasprintf +#define _GNU_MIXER // needed for vasprintf #include "alsa-softmixer.h" #include @@ -24,133 +24,169 @@ // Fulup need to be cleanup with new controller version extern Lua2cWrapperT Lua2cWrap; -STATIC int ProcessOneChannel(CtlSourceT *source, const char* uid, json_object *channelJ, AlsaPcmChannelT *channel) { - const char*channelUid; +PUBLIC AlsaSndZoneT *ApiZoneGetByUid(SoftMixerT *mixer, const char *target) { - int error = wrap_json_unpack(channelJ, "{ss,si,s?i !}", "target", &channelUid, "channel", &channel->port); + assert(mixer->zones[0]); + + // search for subdev into every registered loop + for (int idx = 0; mixer->zones[idx]; idx++) { + if (mixer->zones[idx]->uid && !strcasecmp(mixer->zones[idx]->uid, target)) { + return mixer->zones[idx]; + } + } + AFB_ApiError(mixer->api, "ApiZoneGetByUid mixer=%s fail to find zone=%s", mixer->uid, target); + return NULL; +} + +STATIC AlsaPcmChannelT* ProcessOneChannel(SoftMixerT *mixer, const char* uid, json_object *channelJ) { + AlsaPcmChannelT *channel = calloc(1, sizeof (AlsaPcmChannelT)); + + int error = wrap_json_unpack(channelJ, "{ss,si !}" + , "target", &channel->uid + , "channel", &channel->port + ); if (error) goto OnErrorExit; - channel->uid = strdup(channelUid); - return 0; + channel->uid = strdup(channel->uid); + return channel; OnErrorExit: - AFB_ApiError(source->api, "ProcessOneChannel: zone=%s channel: missing (target||channel) json=%s", uid, json_object_get_string(channelJ)); - return -1; + AFB_ApiError(mixer->api, "ProcessOneChannel: zone=%s channel: missing (target|channel) json=%s", uid, json_object_get_string(channelJ)); + return NULL; } -STATIC int ProcessOneZone(CtlSourceT *source, json_object *zoneJ, AlsaSndZoneT *zone) { - json_object *mappingJ; +STATIC AlsaSndZoneT *AttacheOneZone(SoftMixerT *mixer, const char *uid, json_object *zoneJ) { + AlsaSndZoneT *zone = calloc(1, sizeof (AlsaSndZoneT)); + json_object *sinkJ = NULL, *sourceJ = NULL; size_t count; - const char* streamType; int error; - error = wrap_json_unpack(zoneJ, "{ss,s?s,so !}" + error = wrap_json_unpack(zoneJ, "{ss,s?o,s?o !}" , "uid", &zone->uid - , "type", &streamType - , "mapping", &mappingJ + , "sink", &sinkJ + , "source", &sourceJ ); - if (error) { - AFB_ApiNotice(source->api, "ProcessOneZone missing 'uid|type|mapping' zone=%s", json_object_get_string(zoneJ)); + if (error || (!sinkJ && sourceJ)) { + AFB_ApiNotice(mixer->api, "AttacheOneZone missing 'uid|sink|source' error=%s zone=%s", wrap_json_get_error_string(error), 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 (AlsaPcmChannelT)); - 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 (AlsaPcmChannelT)); - 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; + if (sinkJ) { + + switch (json_object_get_type(sinkJ)) { + case json_type_object: + zone->sinks = calloc(2, sizeof (void*)); + zone->sinks[0] = ProcessOneChannel(mixer, zone->uid, sinkJ); + if (!zone->sinks[0]) goto OnErrorExit; + + break; + case json_type_array: + count = json_object_array_length(sinkJ); + zone->sinks = calloc(count + 1, sizeof (void*)); + for (int idx = 0; idx < count; idx++) { + json_object *subdevJ = json_object_array_get_idx(sinkJ, idx); + zone->sinks[idx] = ProcessOneChannel(mixer, zone->uid, subdevJ); + if (error) goto OnErrorExit; + } + break; + default: + AFB_ApiError(mixer->api, "AttacheOneZone: Mixer=%s Hal=%s zone=%s invalid mapping=%s", mixer->uid, uid, zone->uid, json_object_get_string(sinkJ)); + goto OnErrorExit; + } + } - return 0; + if (sourceJ) { + switch (json_object_get_type(sourceJ)) { + case json_type_object: + zone->sources = calloc(2, sizeof (void*)); + zone->sources[0] = ProcessOneChannel(mixer, zone->uid, sourceJ); + if (!zone->sources[0]) goto OnErrorExit; + break; + case json_type_array: + count = json_object_array_length(sourceJ); + zone->sources = calloc(count + 1, sizeof (void*)); + for (int idx = 0; idx < count; idx++) { + json_object *subdevJ = json_object_array_get_idx(sourceJ, idx); + zone->sources[idx] = ProcessOneChannel(mixer, zone->uid, subdevJ); + if (error) goto OnErrorExit; + } + break; + default: + AFB_ApiError(mixer->api, "AttacheOneZone:Mixer=%s Hal=%s zone=%s mapping=%s", mixer->uid, uid, zone->uid, json_object_get_string(sourceJ)); + goto OnErrorExit; + } + } + + return zone; OnErrorExit: - return -1; + return NULL; } -PUBLIC int SndZones(CtlSourceT *source, json_object *argsJ) { - SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; - AlsaSndZoneT *zones=NULL; - int error; - size_t count; +PUBLIC int ApiZoneAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ) { - assert(mixerHandle); + int index; + for (index = 0; index < mixer->max.zones; index++) { + if (!mixer->zones[index]) break; + } - if (mixerHandle->routes) { - AFB_ApiError(source->api, "SndZones: mixer=%s Zones already Registryed %s", mixerHandle->uid, json_object_get_string(argsJ)); + if (index == mixer->max.zones) { + AFB_ReqFailF(request, "too-small", "mixer=%s max zone=%d argsJ= %s", mixer->uid, mixer->max.zones, json_object_get_string(argsJ)); goto OnErrorExit; } switch (json_object_get_type(argsJ)) { + long count; + 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)); + mixer->zones[index] = AttacheOneZone(mixer, uid, argsJ); + if (!mixer->zones[index]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s invalid zone= %s", mixer->uid, json_object_get_string(argsJ)); goto OnErrorExit; } + + AlsaPcmCtlT *routeConfig = AlsaCreateRoute(mixer, mixer->zones[index], 0); + if (!routeConfig) { + AFB_ApiError(mixer->api, "AttacheOneZone: Mixer=%s Hal=%s zone=%s Fail to attach PCM Route", mixer->uid, uid, mixer->zones[index]->uid); + goto OnErrorExit; + } + break; case json_type_array: count = json_object_array_length(argsJ); - zones = calloc(count + 1, sizeof (AlsaSndZoneT)); + if (count > (mixer->max.zones - count)) { + AFB_ReqFailF(request, "too-small", "mixer=%s max zone=%d argsJ= %s", mixer->uid, mixer->max.zones, json_object_get_string(argsJ)); + goto OnErrorExit; + + } + 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)); + json_object *zoneJ = json_object_array_get_idx(argsJ, idx); + mixer->zones[index + idx] = AttacheOneZone(mixer, uid, zoneJ); + if (!mixer->zones[index + idx]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s invalid zone= %s", mixer->uid, json_object_get_string(zoneJ)); + goto OnErrorExit; + } + + AlsaPcmCtlT *routeConfig = AlsaCreateRoute(mixer, mixer->zones[idx], 0); + if (!routeConfig) { + AFB_ApiError(mixer->api, "AttacheOneZone: Mixer=%s Hal=%s zone=%s Fail to attach PCM Route", mixer->uid, uid, mixer->zones[idx]->uid); goto OnErrorExit; } } break; default: - AFB_ApiError(source->api, "SndZones: mixer=%s invalid argsJ= %s", mixerHandle->uid, json_object_get_string(argsJ)); + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s zones invalid argsJ= %s", mixer->uid, json_object_get_string(argsJ)); goto OnErrorExit; } - // Registry 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 4ba871a..0ef11e1 100644 --- a/plugins/alsa/alsa-core-ctl.c +++ b/plugins/alsa/alsa-core-ctl.c @@ -29,18 +29,14 @@ for the specific language governing permissions and #include typedef struct { - AFB_ApiT api; + SoftMixerT *mixer; sd_event_source* evtsrc; - pthread_t thread; - int tid; - char* info; - snd_ctl_t *ctlDev; + const char* uid; + AlsaSndCtlT *sndcard; sd_event *sdLoop; - RegistryHandleT *registry; } SubscribeHandleT; - -PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNumidElemId(CtlSourceT *source, snd_ctl_t* ctlDev, int numid) { +PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNumidElemId(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numid) { char string[32]; int error; int index; @@ -49,19 +45,19 @@ PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNumidElemId(CtlSourceT *source, snd_ctl_t* c snd_ctl_elem_list_alloca(&ctlList); - if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { - AFB_ApiError(source->api, "AlsaCtlGetNumidElemId [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string)); + if ((error = snd_ctl_elem_list(sndcard->ctl, ctlList)) < 0) { + AFB_ApiError(mixer->api, "AlsaCtlGetNumidElemId [%s] fail retrieve controls", ALSA_CTL_UID(sndcard->ctl, string)); goto OnErrorExit; } if ((error = snd_ctl_elem_list_alloc_space(ctlList, snd_ctl_elem_list_get_count(ctlList))) < 0) { - AFB_ApiError(source->api, "AlsaCtlGetNumidElemId [%s] fail retrieve count", ALSA_CTL_UID(ctlDev, string)); + AFB_ApiError(mixer->api, "AlsaCtlGetNumidElemId [%s] fail retrieve count", ALSA_CTL_UID(sndcard->ctl, string)); goto OnErrorExit; } // Fulup: do not understand why snd_ctl_elem_list should be call twice to get a valid ctlCount - if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { - AFB_ApiError(source->api, "AlsaCtlGetNumidElemId [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string)); + if ((error = snd_ctl_elem_list(sndcard->ctl, ctlList)) < 0) { + AFB_ApiError(mixer->api, "AlsaCtlGetNumidElemId [%s] fail retrieve controls", ALSA_CTL_UID(sndcard->ctl, string)); goto OnErrorExit; } @@ -77,7 +73,7 @@ PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNumidElemId(CtlSourceT *source, snd_ctl_t* c } if (index == ctlCount) { - AFB_ApiNotice(source->api, "AlsaCtlGetNumidElemId [%s] fail get numid=%i count", ALSA_CTL_UID(ctlDev, string), numid); + AFB_ApiNotice(mixer->api, "AlsaCtlGetNumidElemId [%s] fail get numid=%i count", ALSA_CTL_UID(sndcard->ctl, string), numid); goto OnErrorExit; } @@ -90,8 +86,7 @@ OnErrorExit: return NULL; } -PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(CtlSourceT *source, snd_ctl_t* ctlDev, const char *ctlName) { - char string[32]; +PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName) { int error; int index; snd_ctl_elem_list_t *ctlList = NULL; @@ -99,19 +94,19 @@ PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(CtlSourceT *source, snd_ctl_t* ct snd_ctl_elem_list_alloca(&ctlList); - if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { - AFB_ApiError(source->api, "AlsaCtlGetNameElemId [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string)); + if ((error = snd_ctl_elem_list(sndcard->ctl, ctlList)) < 0) { + AFB_ApiError(mixer->api, "AlsaCtlGetNameElemId cardid='%s' cardname='%s' fail retrieve controls", sndcard->cid.cardid, sndcard->cid.name); goto OnErrorExit; } if ((error = snd_ctl_elem_list_alloc_space(ctlList, snd_ctl_elem_list_get_count(ctlList))) < 0) { - AFB_ApiError(source->api, "AlsaCtlGetNameElemId [%s] fail retrieve count", ALSA_CTL_UID(ctlDev, string)); + AFB_ApiError(mixer->api, "AlsaCtlGetNameElemId cardid='%s' cardname='%s' fail retrieve count", sndcard->cid.cardid, sndcard->cid.name); goto OnErrorExit; } // Fulup: do not understand why snd_ctl_elem_list should be call twice to get a valid ctlCount - if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { - AFB_ApiError(source->api, "AlsaCtlGetNameElemId [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string)); + if ((error = snd_ctl_elem_list(sndcard->ctl, ctlList)) < 0) { + AFB_ApiError(mixer->api, "AlsaCtlGetNameElemId cardid='%s' cardname='%s' fail retrieve controls", sndcard->cid.cardid, sndcard->cid.name); goto OnErrorExit; } @@ -119,7 +114,7 @@ PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(CtlSourceT *source, snd_ctl_t* ct int ctlCount = snd_ctl_elem_list_get_used(ctlList); for (index = 0; index < ctlCount; index++) { - if (!strcasecmp(ctlName, snd_ctl_elem_list_get_name(ctlList, index))) { + if (strcasestr(snd_ctl_elem_list_get_name(ctlList, index), ctlName)) { snd_ctl_elem_id_malloc(&elemId); snd_ctl_elem_list_get_id(ctlList, index, elemId); break; @@ -127,7 +122,7 @@ PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(CtlSourceT *source, snd_ctl_t* ct } if (index == ctlCount) { - AFB_ApiNotice(source->api, "AlsaCtlGetNameElemId [%s] ctl not found name=%s", ALSA_CTL_UID(ctlDev, string), ctlName); + AFB_ApiNotice(mixer->api, "AlsaCtlGetNameElemId cardid='%s' cardname='%s' ctl not found name=%s", sndcard->cid.cardid, sndcard->cid.name, ctlName); goto OnErrorExit; } @@ -140,42 +135,26 @@ OnErrorExit: return NULL; } -PUBLIC snd_ctl_t *AlsaCtlOpenCtl(CtlSourceT *source, const char *cardid) { +PUBLIC snd_ctl_t *AlsaCtlOpenCtl(SoftMixerT *mixer, const char *cardid) { int error; - snd_ctl_t *ctlDev; + snd_ctl_t *ctl; if (!cardid) goto OnErrorExit; - if ((error = snd_ctl_open(&ctlDev, cardid, SND_CTL_READONLY)) < 0) { + if ((error = snd_ctl_open(&ctl, cardid, SND_CTL_READONLY)) < 0) { cardid = "Not Defined"; goto OnErrorExit; } - return ctlDev; + return ctl; OnErrorExit: - AFB_ApiError(source->api, "AlsaCtlOpenCtl: fail to find sndcard by id= %s", cardid); + AFB_ApiError(mixer->api, "AlsaCtlOpenCtl: fail to find sndcard by id= %s", cardid); 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; - - snd_ctl_elem_info_alloca(&elemInfo); - snd_ctl_elem_info_set_id(elemInfo, elemId); - if (snd_ctl_elem_info(ctlDev, elemInfo) < 0) goto OnErrorExit; - if (!snd_ctl_elem_info_is_readable(elemInfo)) goto OnErrorExit; - - *numid = snd_ctl_elem_info_get_numid(elemInfo); - - return 0; - -OnErrorExit: - return -1; -} - -STATIC void CtlElemIdDisplay(AFB_ApiT api, snd_ctl_elem_info_t *elemInfo, snd_ctl_elem_value_t *elemData) { +STATIC void CtlElemIdDisplay(SoftMixerT *mixer, snd_ctl_elem_info_t *elemInfo, snd_ctl_elem_value_t *elemData) { int numid = snd_ctl_elem_info_get_numid(elemInfo); int count = snd_ctl_elem_info_get_count(elemInfo); @@ -184,7 +163,7 @@ STATIC void CtlElemIdDisplay(AFB_ApiT api, snd_ctl_elem_info_t *elemInfo, snd_ct if (!elemData) { - AFB_ApiWarning(api, "CtlElemIdDisplay: numid=%d name=%s value=unreadable", numid, name); + AFB_ApiWarning(mixer->api, "CtlElemIdDisplay: numid=%d name=%s value=unreadable", numid, name); } else for (int idx = 0; idx < count; idx++) { long valueL; @@ -192,67 +171,63 @@ STATIC void CtlElemIdDisplay(AFB_ApiT api, snd_ctl_elem_info_t *elemInfo, snd_ct switch (elemType) { case SND_CTL_ELEM_TYPE_BOOLEAN: valueL = snd_ctl_elem_value_get_boolean(elemData, idx); - AFB_ApiWarning(api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); + AFB_ApiWarning(mixer->api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); break; case SND_CTL_ELEM_TYPE_INTEGER: valueL = snd_ctl_elem_value_get_integer(elemData, idx); - AFB_ApiWarning(api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); + AFB_ApiWarning(mixer->api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); break; case SND_CTL_ELEM_TYPE_INTEGER64: valueL = snd_ctl_elem_value_get_integer64(elemData, idx); - AFB_ApiWarning(api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); + AFB_ApiWarning(mixer->api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); break; case SND_CTL_ELEM_TYPE_ENUMERATED: valueL = snd_ctl_elem_value_get_enumerated(elemData, idx); - AFB_ApiWarning(api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); + AFB_ApiWarning(mixer->api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); break; case SND_CTL_ELEM_TYPE_BYTES: valueL = snd_ctl_elem_value_get_byte(elemData, idx); - AFB_ApiWarning(api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); + AFB_ApiWarning(mixer->api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); break; case SND_CTL_ELEM_TYPE_IEC958: default: - AFB_ApiWarning(api, "CtlElemIdDisplay: numid=%d name=%s Unsupported type=%d", numid, name, elemType); + AFB_ApiWarning(mixer->api, "CtlElemIdDisplay: numid=%d name=%s Unsupported type=%d", numid, name, elemType); break; } } } -PUBLIC int CtlElemIdGetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, long *value) { +PUBLIC int CtlElemIdGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, snd_ctl_elem_id_t *elemId, long *value) { int error; snd_ctl_elem_value_t *elemData; snd_ctl_elem_info_t *elemInfo; snd_ctl_elem_info_alloca(&elemInfo); snd_ctl_elem_info_set_id(elemInfo, elemId); - if (snd_ctl_elem_info(ctlDev, elemInfo) < 0) goto OnErrorExit; + if (snd_ctl_elem_info(sndcard->ctl, elemInfo) < 0) goto OnErrorExit; if (!snd_ctl_elem_info_is_readable(elemInfo)) goto OnErrorExit; // as we have static rate/channel we should have only one boolean as value snd_ctl_elem_value_alloca(&elemData); snd_ctl_elem_value_set_id(elemData, elemId); - error = snd_ctl_elem_read(ctlDev, elemData); + error = snd_ctl_elem_read(sndcard->ctl, elemData); if (error) { elemData = NULL; goto OnErrorExit; } - // warning multi channel are always view as grouped - //int count = snd_ctl_elem_info_get_count(elemInfo); - //if (count != 1) goto OnErrorExit; - // value=1 when active and 0 when not active *value = (int) snd_ctl_elem_value_get_integer(elemData, 0); return 0; OnErrorExit: - CtlElemIdDisplay(api, elemInfo, elemData); + CtlElemIdDisplay(mixer, elemInfo, elemData); return -1; } -PUBLIC int CtlElemIdSetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, long value) { +PUBLIC int CtlElemIdSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, snd_ctl_elem_id_t *elemId, long value) { snd_ctl_elem_value_t *elemData; snd_ctl_elem_info_t *elemInfo; const char* name; @@ -260,7 +235,7 @@ PUBLIC int CtlElemIdSetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t * snd_ctl_elem_info_alloca(&elemInfo); snd_ctl_elem_info_set_id(elemInfo, elemId); - if (snd_ctl_elem_info(ctlDev, elemInfo) < 0) goto OnErrorExit; + if (snd_ctl_elem_info(sndcard->ctl, elemInfo) < 0) goto OnErrorExit; if (!snd_ctl_elem_info_is_writable(elemInfo)) goto OnErrorExit; @@ -269,15 +244,14 @@ PUBLIC int CtlElemIdSetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t * snd_ctl_elem_value_alloca(&elemData); snd_ctl_elem_value_set_id(elemData, elemId); - error = snd_ctl_elem_read(ctlDev, elemData); + error = snd_ctl_elem_read(sndcard->ctl, elemData); if (error) goto OnErrorExit; - for (int index = 0; index < count; index++) { snd_ctl_elem_value_set_integer(elemData, index, value); } - error = snd_ctl_elem_write(ctlDev, elemData); + error = snd_ctl_elem_write(sndcard->ctl, elemData); if (error) goto OnErrorExit; return 0; @@ -285,45 +259,43 @@ PUBLIC int CtlElemIdSetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t * OnErrorExit: numid = snd_ctl_elem_info_get_numid(elemInfo); name = snd_ctl_elem_info_get_name(elemInfo); - AFB_ApiError(api, "CtlElemIdSetInt: numid=%d name=%s not writable", numid, name); + AFB_ApiError(mixer->api, "CtlElemIdSetInt: numid=%d name=%s not writable", numid, name); return -1; } // Clone of AlsaLib snd_card_load2 static function -PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(CtlSourceT *source, const char *cardid) { +PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(SoftMixerT *mixer, const char *cardid) { int error; - snd_ctl_t *ctlDev; - - if (cardid) goto OnErrorExit; + snd_ctl_t *ctl; - if ((error = snd_ctl_open(&ctlDev, cardid, SND_CTL_READONLY)) < 0) { + if ((error = snd_ctl_open(&ctl, cardid, SND_CTL_READONLY)) < 0) { cardid = "Not Defined"; goto OnErrorExit; } snd_ctl_card_info_t *cardInfo = malloc(snd_ctl_card_info_sizeof()); - if ((error = snd_ctl_card_info(ctlDev, cardInfo)) < 0) { + if ((error = snd_ctl_card_info(ctl, cardInfo)) < 0) { goto OnErrorExit; } return cardInfo; OnErrorExit: - AFB_ApiError(source->api, "AlsaCtlGetInfo: fail to find sndcard by id= %s", cardid); + AFB_ApiError(mixer->api, "AlsaCtlGetInfo: fail to find sndcard by id= %s", cardid); return NULL; } -PUBLIC int AlsaCtlNumidSetLong(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long value) { +PUBLIC int AlsaCtlNumidSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numid, long value) { - snd_ctl_elem_id_t *elemId = AlsaCtlGetNumidElemId(source, ctlDev, numid); + snd_ctl_elem_id_t *elemId = AlsaCtlGetNumidElemId(mixer, sndcard, numid); if (!elemId) { - AFB_ApiError(source->api, "AlsaCtlNumidSetLong [sndcard=%s] fail to find numid=%d", snd_ctl_name(ctlDev), numid); + AFB_ApiError(mixer->api, "AlsaCtlNumidSetLong cardid=%s cardname=%s fail to find numid=%d", sndcard->cid.cardid, sndcard->cid.longname, numid); goto OnErrorExit; } - int error = CtlElemIdSetLong(source->api, ctlDev, elemId, value); + int error = CtlElemIdSetLong(mixer, sndcard, elemId, value); if (error) { - AFB_ApiError(source->api, "AlsaCtlNumidSetLong [sndcard=%s] fail to set numid=%d value=%ld", snd_ctl_name(ctlDev), numid, value); + AFB_ApiError(mixer->api, "AlsaCtlNumidSetLong cardid=%s cardname=%s fail to set numid=%d value=%ld", sndcard->cid.cardid, sndcard->cid.longname, numid, value); goto OnErrorExit; } @@ -332,17 +304,17 @@ OnErrorExit: return -1; } -PUBLIC int AlsaCtlNumidGetLong(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long* value) { +PUBLIC int AlsaCtlNumidGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numid, long* value) { - snd_ctl_elem_id_t *elemId = AlsaCtlGetNumidElemId(source, ctlDev, numid); + snd_ctl_elem_id_t *elemId = AlsaCtlGetNumidElemId(mixer, sndcard, numid); if (!elemId) { - AFB_ApiError(source->api, "AlsaCtlNumidGetLong [sndcard=%s] fail to find numid=%d", snd_ctl_name(ctlDev), numid); + AFB_ApiError(mixer->api, "AlsaCtlNumidGetLong cardid=%s cardname=%s fail to find numid=%d", sndcard->cid.cardid, sndcard->cid.longname, numid); goto OnErrorExit; } - int error = CtlElemIdGetLong(source->api, ctlDev, elemId, value); + int error = CtlElemIdGetLong(mixer, sndcard, elemId, value); if (error) { - AFB_ApiError(source->api, "AlsaCtlNumidGetLong [sndcard=%s] fail to get numid=%d value", snd_ctl_name(ctlDev), numid); + AFB_ApiError(mixer->api, "AlsaCtlNumidGetLong cardid=%s cardname=%s fail to get numid=%d value", sndcard->cid.cardid, sndcard->cid.longname, numid); goto OnErrorExit; } @@ -351,17 +323,17 @@ OnErrorExit: return -1; } -PUBLIC int AlsaCtlNameSetLong(CtlSourceT *source, snd_ctl_t* ctlDev, const char *ctlName, long value) { +PUBLIC int AlsaCtlNameSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName, long value) { - snd_ctl_elem_id_t *elemId = AlsaCtlGetNameElemId(source, ctlDev, ctlName); + snd_ctl_elem_id_t *elemId = AlsaCtlGetNameElemId(mixer, sndcard, ctlName); if (!elemId) { - AFB_ApiError(source->api, "AlsaCtlNameSetLong [sndcard=%s] fail to find crlName=%s", snd_ctl_name(ctlDev), ctlName); + AFB_ApiError(mixer->api, "AlsaCtlNameSetLong cardid=%s cardname=%s fail to find crlName=%s", sndcard->cid.cardid, sndcard->cid.longname, ctlName); goto OnErrorExit; } - int error = CtlElemIdSetLong(source->api, ctlDev, elemId, value); + int error = CtlElemIdSetLong(mixer, sndcard, elemId, value); if (error) { - AFB_ApiError(source->api, "AlsaCtlNameSetLong [sndcard=%s] fail to set crlName=%s value=%ld", snd_ctl_name(ctlDev), ctlName, value); + AFB_ApiError(mixer->api, "AlsaCtlNameSetLong cardid=%s cardname=%s fail to set crlName=%s value=%ld", sndcard->cid.cardid, sndcard->cid.longname, ctlName, value); goto OnErrorExit; } @@ -370,18 +342,17 @@ OnErrorExit: return -1; } +PUBLIC int AlsaCtlNameGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName, long* value) { -PUBLIC int AlsaCtlNameGetLong(CtlSourceT *source, snd_ctl_t* ctlDev, const char *ctlName, long* value) { - - snd_ctl_elem_id_t *elemId = AlsaCtlGetNameElemId(source, ctlDev, ctlName); + snd_ctl_elem_id_t *elemId = AlsaCtlGetNameElemId(mixer, sndcard, ctlName); if (!elemId) { - AFB_ApiError(source->api, "AlsaCtlNameGetLong [sndcard=%s] fail to find crlName=%s", snd_ctl_name(ctlDev), ctlName); + AFB_ApiError(mixer->api, "AlsaCtlNameGetLong cardid=%s cardname=%s fail to find crlName=%s", sndcard->cid.cardid, sndcard->cid.longname, ctlName); goto OnErrorExit; } - int error = CtlElemIdGetLong(source->api, ctlDev, elemId, value); + int error = CtlElemIdGetLong(mixer, sndcard, elemId, value); if (error) { - AFB_ApiError(source->api, "AlsaCtlNameGetLong [sndcard=%s] fail to get crlName=%s value", snd_ctl_name(ctlDev), ctlName); + AFB_ApiError(mixer->api, "AlsaCtlNameGetLong cardid=%s cardname=%s fail to get crlName=%s value", sndcard->cid.cardid, sndcard->cid.longname, ctlName); goto OnErrorExit; } @@ -390,7 +361,7 @@ OnErrorExit: return -1; } -STATIC int AlsaCtlMakeControl(CtlSourceT *source, snd_ctl_t* ctlDev, AlsaPcmInfoT *subdev, const char *ctlName, int ctlCount, int ctlMin, int ctlMax, int ctlStep) { +STATIC int AlsaCtlMakeControl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName, int ctlCount, int ctlMin, int ctlMax, int ctlStep) { snd_ctl_elem_type_t ctlType; snd_ctl_elem_info_t *elemInfo; int error; @@ -398,11 +369,11 @@ STATIC int AlsaCtlMakeControl(CtlSourceT *source, snd_ctl_t* ctlDev, AlsaPcmInfo snd_ctl_elem_info_alloca(&elemInfo); if (ctlName) snd_ctl_elem_info_set_name(elemInfo, ctlName); snd_ctl_elem_info_set_interface(elemInfo, SND_CTL_ELEM_IFACE_MIXER); - snd_ctl_elem_info(ctlDev, elemInfo); - + snd_ctl_elem_info(sndcard->ctl, elemInfo); + // map volume to sndcard device+subdev=0 - snd_ctl_elem_info_set_device(elemInfo, subdev->device); - snd_ctl_elem_info_set_subdevice(elemInfo, subdev->subdev); + snd_ctl_elem_info_set_device(elemInfo, sndcard->cid.device); + snd_ctl_elem_info_set_subdevice(elemInfo, sndcard->cid.subdev); snd_ctl_elem_info_set_device(elemInfo, 0); snd_ctl_elem_info_set_subdevice(elemInfo, 0); @@ -412,17 +383,17 @@ STATIC int AlsaCtlMakeControl(CtlSourceT *source, snd_ctl_t* ctlDev, AlsaPcmInfo switch (ctlType) { case SND_CTL_ELEM_TYPE_BOOLEAN: - error = snd_ctl_add_boolean_elem_set(ctlDev, elemInfo, 1, ctlCount); + error = snd_ctl_add_boolean_elem_set(sndcard->ctl, elemInfo, 1, ctlCount); if (error) goto OnErrorExit; break; case SND_CTL_ELEM_TYPE_INTEGER: - error = snd_ctl_add_integer_elem_set(ctlDev, elemInfo, 1, ctlCount, ctlMin, ctlMax, ctlStep); + error = snd_ctl_add_integer_elem_set(sndcard->ctl, elemInfo, 1, ctlCount, ctlMin, ctlMax, ctlStep); if (error) goto OnErrorExit; break; default: - AFB_ApiError(source->api, "AlsaCtlMakeControl:%s(subdev) fail to create %s(control)", subdev->uid, ctlName); + AFB_ApiError(mixer->api, "AlsaCtlMakeControl: mixer=%s cardid=%s cardname=%s fail to create %s(control)", mixer->uid, sndcard->cid.cardid, sndcard->cid.longname, ctlName); goto OnErrorExit; } @@ -434,46 +405,48 @@ OnErrorExit: return -1; } -PUBLIC int AlsaCtlCreateControl(CtlSourceT *source, snd_ctl_t* ctlDev, AlsaPcmInfoT *subdevs, char* ctlName, int ctlCount, int ctlMin, int ctlMax, int ctlStep, long value) { +PUBLIC int AlsaCtlCreateControl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, char* ctlName, int ctlCount, int ctlMin, int ctlMax, int ctlStep, long value) { int numid = -1; // if control does not exist then create - snd_ctl_elem_id_t *elemId = AlsaCtlGetNameElemId(source, ctlDev, ctlName); + snd_ctl_elem_id_t *elemId = AlsaCtlGetNameElemId(mixer, sndcard, ctlName); if (elemId) { numid = snd_ctl_elem_id_get_numid(elemId); } else { // create or get numid control when already exist - numid = AlsaCtlMakeControl(source, ctlDev, subdevs, ctlName, ctlCount, ctlMin, ctlMax, ctlStep); + numid = AlsaCtlMakeControl(mixer, sndcard, ctlName, ctlCount, ctlMin, ctlMax, ctlStep); if (numid <= 0) { - AFB_ApiError(source->api, "AlsaCtlCreateControl [sndcard=%s] fail to create ctlName=%s", snd_ctl_name(ctlDev), ctlName); + AFB_ApiError(mixer->api, "AlsaCtlCreateControl cardid=%s cardname=%s fail to create ctlName=%s", sndcard->cid.cardid, sndcard->cid.longname, ctlName); goto OnErrorExit; } - elemId = AlsaCtlGetNumidElemId(source, ctlDev, numid); + elemId = AlsaCtlGetNumidElemId(mixer, sndcard, numid); } - int error = CtlElemIdSetLong(source->api, ctlDev, elemId, value); + int error = CtlElemIdSetLong(mixer, sndcard, elemId, value); if (error) { - AFB_ApiError(source->api, "AlsaCtlCreateControl [sndcard=%s] fail to set ctlName=%s Numid=%d", snd_ctl_name(ctlDev), ctlName, numid); + AFB_ApiError(mixer->api, "AlsaCtlCreateControl cardid=%s cardname=%s fail to set ctlName=%s Numid=%d", sndcard->cid.cardid, sndcard->cid.longname, ctlName, numid); goto OnErrorExit; } - AFB_ApiNotice(source->api, "AlsaCtlCreateControl [sndcard=%s] ctl create name=%s numid=%d value=%ld", snd_ctl_name(ctlDev), ctlName, numid, value); + AFB_ApiNotice(mixer->api, "AlsaCtlCreateControl cardid=%s cardname=%s ctl create name=%s numid=%d value=%ld", sndcard->cid.cardid, sndcard->cid.longname, ctlName, numid, value); return numid; OnErrorExit: return -1; } STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData) { - int error, numid; - SubscribeHandleT *subscribeHandle = (SubscribeHandleT*) userData; + int error; + SubscribeHandleT *sHandle = (SubscribeHandleT*) userData; + AlsaSndCtlT *sndcard = sHandle->sndcard; + SoftMixerT *mixer = sHandle->mixer; snd_ctl_event_t *eventId; snd_ctl_elem_id_t *elemId; long value; - int idx; + int index; if ((revents & EPOLLHUP) != 0) { - AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB hanghup [card:%s disconnected]", subscribeHandle->info); + AFB_ApiNotice(mixer->api, "CtlSubscribeEventCB hanghup [card:%s disconnected]", sHandle->uid); goto OnSuccessExit; } @@ -483,7 +456,7 @@ STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, v snd_ctl_event_alloca(&eventId); snd_ctl_elem_id_alloca(&elemId); - error = snd_ctl_read(subscribeHandle->ctlDev, eventId); + error = snd_ctl_read(sndcard->ctl, eventId); if (error < 0) goto OnErrorExit; // we only process sndctrl element @@ -495,73 +468,52 @@ STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, v // extract element from event and get value snd_ctl_event_elem_get_id(eventId, elemId); - error = CtlElemIdGetLong(subscribeHandle->api, subscribeHandle->ctlDev, elemId, &value); + error = CtlElemIdGetLong(mixer, sHandle->sndcard, elemId, &value); if (error) goto OnErrorExit; + + // get numdid and name from elemId + snd_ctl_elem_info_t *elemInfo; + snd_ctl_elem_info_alloca(&elemInfo); + snd_ctl_elem_info_set_id(elemInfo, elemId); + if (snd_ctl_elem_info(sndcard->ctl, elemInfo) < 0) goto OnErrorExit; + int numid = snd_ctl_elem_info_get_numid(elemInfo); + const char *name= snd_ctl_elem_info_get_name(elemInfo); - error = CtlElemIdGetNumid(subscribeHandle->api, subscribeHandle->ctlDev, elemId, &numid); - if (error) goto OnErrorExit; + for (index = 0; sndcard->registry[index]; index++) { + if (sndcard->registry[index]->numid == numid) { - for (idx = 0; idx < subscribeHandle->registry->count; idx++) { - if (subscribeHandle->registry->stream[idx].numid == numid) { - const char *pcmName = subscribeHandle->registry->stream[idx].pcm->cardid; - - switch (subscribeHandle->registry->stream[idx].type) { - case FONTEND_NUMID_RUN: - snd_pcm_pause(subscribeHandle->registry->stream[idx].pcm->handle, (int)(!value)); - AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d pcm=%s numid=%d active=%ld", subscribeHandle->info, subscribeHandle->tid, pcmName, numid, value); + switch (sndcard->registry[index]->type) { + case FONTEND_NUMID_RUN: + (void) snd_pcm_pause(sndcard->registry[index]->pcm->handle, (int) (!value)); + AFB_ApiNotice(mixer->api, "CtlSubscribeEventCB:%s numid=%d name=%s active=%ld", sHandle->uid, numid, name, value); break; - case FONTEND_NUMID_PAUSE: - AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d pcm=%s numid=%d pause=%ld", subscribeHandle->info, subscribeHandle->tid, pcmName, numid, value); - snd_pcm_pause(subscribeHandle->registry->stream[idx].pcm->handle, (int)value); + case FONTEND_NUMID_PAUSE: + AFB_ApiNotice(mixer->api, "CtlSubscribeEventCB:%s numid=%d name=%s pause=%ld", sHandle->uid, numid, name, value); + (void) snd_pcm_pause(sndcard->registry[index]->pcm->handle, (int) value); break; case FONTEND_NUMID_IGNORE: - default: - AFB_ApiInfo(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d pcm=%s numid=%d ignored=%ld", subscribeHandle->info, subscribeHandle->tid, pcmName, numid, value); + default: + AFB_ApiInfo(mixer->api, "CtlSubscribeEventCB:%s numid=%d name=%s ignored=%ld", sHandle->uid, numid, name, value); } break; } } - if (idx == subscribeHandle->registry->count) { - char cardName[32]; - ALSA_CTL_UID(subscribeHandle->ctlDev, cardName); - AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d card=%s numid=%d (unknown)", subscribeHandle->info, subscribeHandle->tid, cardName, numid); + if (index == sndcard->rcount) { + AFB_ApiNotice(mixer->api, "CtlSubscribeEventCB:%s numid=%d (unknown)", sHandle->uid, numid); } OnSuccessExit: return 0; OnErrorExit: - AFB_ApiInfo(subscribeHandle->api, "CtlSubscribeEventCB: ignored unsupported event"); + AFB_ApiInfo(mixer->api, "CtlSubscribeEventCB: ignored unsupported event"); return 0; } -static void *LoopInThread(void *handle) { - SubscribeHandleT *subscribeHandle = (SubscribeHandleT*) handle; - int count = 0; - int watchdog = MAINLOOP_WATCHDOG * 1000; - subscribeHandle->tid = (int) syscall(SYS_gettid); - - AFB_ApiNotice(subscribeHandle->api, "LoopInThread:%s/%d Started", subscribeHandle->info, subscribeHandle->tid); - - /* loop until end */ - for (;;) { - int res = sd_event_run(subscribeHandle->sdLoop, watchdog); - if (res == 0) { - AFB_ApiInfo(subscribeHandle->api, "LoopInThread:%s/%d Idle count=%d", subscribeHandle->info, subscribeHandle->tid, count++); - continue; - } - if (res < 0) { - AFB_ApiError(subscribeHandle->api, "LoopInThread:%s/%d ERROR=%i Exit errno=%s", subscribeHandle->info, subscribeHandle->tid, res, strerror(res)); - break; - } - } - pthread_exit(0); -} - -PUBLIC snd_ctl_t* AlsaCrlFromPcm(CtlSourceT *source, snd_pcm_t *pcm) { +PUBLIC snd_ctl_t* AlsaCrlFromPcm(SoftMixerT *mixer, snd_pcm_t *pcm) { char buffer[32]; int error; - snd_ctl_t *ctlDev; + snd_ctl_t *ctl; snd_pcm_info_t *pcmInfo; snd_pcm_info_alloca(&pcmInfo); @@ -569,53 +521,40 @@ PUBLIC snd_ctl_t* AlsaCrlFromPcm(CtlSourceT *source, snd_pcm_t *pcm) { int pcmCard = snd_pcm_info_get_card(pcmInfo); snprintf(buffer, sizeof (buffer), "hw:%i", pcmCard); - if ((error = snd_ctl_open(&ctlDev, buffer, SND_CTL_READONLY)) < 0) goto OnErrorExit; + if ((error = snd_ctl_open(&ctl, buffer, SND_CTL_READONLY)) < 0) goto OnErrorExit; - return ctlDev; + return ctl; OnErrorExit: return NULL; } -PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t * ctlDev, RegistryHandleT *registry) { +PUBLIC int AlsaCtlSubscribe(SoftMixerT *mixer, const char *uid, AlsaSndCtlT *sndcard) { int error; char string [32]; struct pollfd pfds; - SubscribeHandleT *subscribeHandle = malloc(sizeof (SubscribeHandleT)); + SubscribeHandleT *handle = malloc(sizeof (SubscribeHandleT)); - subscribeHandle->api = source->api; - subscribeHandle->ctlDev = ctlDev; - subscribeHandle->info = "ctlEvt"; - subscribeHandle->registry= registry; + handle->mixer = mixer; + handle->sndcard = sndcard; + handle->uid = uid; // 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)); + if ((error = snd_ctl_subscribe_events(handle->sndcard->ctl, 1)) < 0) { + AFB_ApiError(mixer->api, "AlsaCtlSubscribe: fail sndcard=%s to subscribe events", ALSA_CTL_UID(handle->sndcard->ctl, string)); goto OnErrorExit; } // get pollfd attach to this sound board - int count = snd_ctl_poll_descriptors(ctlDev, &pfds, 1); + int count = snd_ctl_poll_descriptors(handle->sndcard->ctl, &pfds, 1); if (count != 1) { - AFB_ApiError(source->api, "AlsaCtlSubscribe: fail sndcard=%s get poll descriptors", ALSA_CTL_UID(ctlDev, string)); - goto OnErrorExit; - } - - // add poll descriptor to AGL systemd mainloop - if ((error = sd_event_new(&subscribeHandle->sdLoop)) < 0) { - fprintf(stderr, "AlsaCtlSubscribe: fail sndcard=%s creating a new loop", ALSA_CTL_UID(ctlDev, string)); + AFB_ApiError(mixer->api, "AlsaCtlSubscribe: fail sndcard=%s get poll descriptors", ALSA_CTL_UID(handle->sndcard->ctl, string)); goto OnErrorExit; } // Registry sound event to binder main loop - if ((error = sd_event_add_io(subscribeHandle->sdLoop, &subscribeHandle->evtsrc, pfds.fd, EPOLLIN, CtlSubscribeEventCB, subscribeHandle)) < 0) { - AFB_ApiError(source->api, "AlsaCtlSubscribe: Fail sndcard=%s adding mainloop", ALSA_CTL_UID(ctlDev, string)); - goto OnErrorExit; - } - - // start a thread with a mainloop to monitor Audio-Agent - if ((error = pthread_create(&subscribeHandle->thread, NULL, &LoopInThread, subscribeHandle)) < 0) { - AFB_ApiError(source->api, "AlsaCtlSubscribe: Fail sndcard=%s create waiting thread err=%d", ALSA_CTL_UID(ctlDev, string), error); + if ((error = sd_event_add_io(mixer->sdLoop, &handle->evtsrc, pfds.fd, EPOLLIN, CtlSubscribeEventCB, handle)) < 0) { + AFB_ApiError(mixer->api, "AlsaCtlSubscribe: Fail sndcard=%s adding mainloop", ALSA_CTL_UID(handle->sndcard->ctl, string)); goto OnErrorExit; } @@ -625,32 +564,29 @@ OnErrorExit: return -1; } -PUBLIC int AlsaCtlRegister(CtlSourceT *source, SoftMixerHandleT *mixer, AlsaPcmInfoT *pcm, RegistryNumidT type, int numid) { +PUBLIC int AlsaCtlRegister(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaPcmCtlT *pcmdev, RegistryNumidT type, int numid) { + int index; - RegistryHandleT *registry= mixer->frontend->registry; + for (index = 0; index < sndcard->rcount; index++) { + if (!sndcard->registry[index]) break; + } - int count = registry->count; - if (count > MAX_AUDIO_STREAMS) { - AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] to many audio stream max=%d", pcm->cardid, MAX_AUDIO_STREAMS); + if (index == sndcard->rcount) { + AFB_ApiError(mixer->api, "AlsaCtlRegister cardid=%s cardname=%s to many audio stream max=%ld", sndcard->cid.cardid, sndcard->cid.longname, sndcard->rcount); goto OnErrorExit; } - // If 1st registration then open a dev control channel to recieve events - if (!registry->ctlDev) { - snd_ctl_t* ctlDev = AlsaCrlFromPcm(source, pcm->handle); - if (!ctlDev) { - AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail attache sndcard", pcm->cardid); - goto OnErrorExit; - } - - AlsaCtlSubscribe(source, ctlDev, registry); + // If 1st registration then register to card event + if (index == 0) { + AlsaCtlSubscribe(mixer, sndcard->cid.cardid, sndcard); } // store PCM in order to pause/resume depending on event - registry->stream[count].pcm = pcm; - registry->stream[count].numid = numid; - registry->stream[count].type = type; - registry->count++; + RegistryEntryPcmT *entry = calloc(1, sizeof (RegistryEntryPcmT)); + sndcard->registry[index] = entry; + entry->pcm = pcmdev; + entry->numid = numid; + entry->type = type; return 0; diff --git a/plugins/alsa/alsa-core-pcm.c b/plugins/alsa/alsa-core-pcm.c index ad8e842..b69d8c6 100644 --- a/plugins/alsa/alsa-core-pcm.c +++ b/plugins/alsa/alsa-core-pcm.c @@ -60,27 +60,30 @@ STATIC int AlsaPeriodSize(snd_pcm_format_t pcmFormat) { return pcmSampleSize; } -PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *opts) { - char string[32]; +PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, AlsaPcmHwInfoT *opts) { int error; snd_pcm_hw_params_t *pxmHwParams; snd_pcm_sw_params_t *pxmSwParams; + snd_pcm_format_t format; + snd_pcm_access_t access; // retrieve hadware config from PCM snd_pcm_hw_params_alloca(&pxmHwParams); snd_pcm_hw_params_any(pcm->handle, pxmHwParams); if (!opts->access) opts->access = SND_PCM_ACCESS_RW_INTERLEAVED; + snd_pcm_hw_params_get_access(pxmHwParams, &access); error = snd_pcm_hw_params_set_access(pcm->handle, pxmHwParams, opts->access); if (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; + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Set_Interleave=%d Fail current=%d mode error=%s", mixer->uid, pcm->cid.cardid, opts->access, access, snd_strerror(error)); +//Fulup goto OnErrorExit; }; if (opts->format != SND_PCM_FORMAT_UNKNOWN) { + snd_pcm_hw_params_get_format(pxmHwParams, &format); if ((error = snd_pcm_hw_params_set_format(pcm->handle, pxmHwParams, opts->format)) < 0) { - 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); + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Set_Format=%d Fail current=%d error=%s", mixer->uid, pcm->cid.cardid, opts->format, format, snd_strerror(error)); + AlsaDumpFormats(mixer, pcm->handle); goto OnErrorExit; } } @@ -88,27 +91,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:%s Fail PCM=%s Set_Rate=%d error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), opts->rate, snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s FailSet_Rate=%d error=%s", mixer->uid, pcm->cid.cardid, opts->rate, snd_strerror(error)); goto OnErrorExit; } // check we got requested rate if (opts->rate != pcmRate) { - 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); + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Set_Rate Fail ask=%dHz get=%dHz", mixer->uid, pcm->cid.cardid,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:%s Fail PCM=%s Set_Channels=%d error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), opts->channels, snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Set_Channels=%d Fail error=%s",mixer->uid, pcm->cid.cardid, 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:%s Fail PCM=%s apply hwparams error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Fail apply hwparams error=%s", mixer->uid, pcm->cid.cardid, snd_strerror(error)); goto OnErrorExit; } @@ -118,7 +121,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:%s Fail PCM=%s unsupported format format=%d", pcm->uid, ALSA_PCM_UID(pcm->handle, string), opts->format); + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Fail unsupported format format=%d", mixer->uid, pcm->cid.cardid, opts->format); goto OnErrorExit; } @@ -127,17 +130,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:%s Fail to PCM=%s set_buffersize error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Fail set_buffersize error=%s", mixer->uid, pcm->cid.cardid, 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:%s Fail to push software=%s params error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Fail to push params error=%s", mixer->uid, pcm->cid.cardid, snd_strerror(error)); goto OnErrorExit; }; - 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); + AFB_ApiNotice(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Done channels=%d rate=%d format=%d access=%d done", mixer->uid, pcm->cid.cardid, opts->channels, opts->rate, opts->format, opts->access); return 0; OnErrorExit: @@ -211,16 +214,19 @@ STATIC int AlsaPcmReadCB(sd_event_source* src, int fd, uint32_t revents, void* u } // effectively read pcmIn and push frame to pcmOut - framesIn = snd_pcm_readi(pcmCopyHandle->pcmIn, pcmCopyHandle->buffer, availIn); + framesIn = snd_pcm_readi(pcmCopyHandle->pcmIn, pcmCopyHandle->buffer, availOut); if (framesIn < 0 || framesIn != availIn) { AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PcmIn=%s UNDERUN frame=%ld", ALSA_PCM_UID(pcmCopyHandle->pcmIn, string), framesIn); + snd_pcm_prepare(pcmCopyHandle->pcmIn); goto ExitOnSuccess; } // In/Out frames transfer through buffer copy - framesOut = snd_pcm_writei(pcmCopyHandle->pcmOut, pcmCopyHandle->buffer, framesIn); + //framesOut = snd_pcm_writei(pcmCopyHandle->pcmOut, pcmCopyHandle->buffer, framesIn); + framesOut = snd_pcm_mmap_writei (pcmCopyHandle->pcmOut, pcmCopyHandle->buffer, framesIn); if (framesOut < 0 || framesOut != framesIn) { AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PcmOut=%s UNDERUN frame=%ld", ALSA_PCM_UID(pcmCopyHandle->pcmOut, string), (framesIn - framesOut)); + snd_pcm_prepare(pcmCopyHandle->pcmOut); goto ExitOnSuccess; } @@ -260,87 +266,92 @@ static void *LoopInThread(void *handle) { pthread_exit(0); } -PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaLoopStreamT *loopStream, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pcmOut, AlsaPcmHwInfoT * opts) { +PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT *pcmIn, AlsaPcmCtlT *pcmOut, AlsaPcmHwInfoT * opts) { char string[32]; struct pollfd *pcmInFds; int error; + + // Fulup need to check https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m___direct.html + + AlsaDumpPcmInfo(mixer,"PcmIn",pcmIn->handle); + AlsaDumpPcmInfo(mixer,"PcmOut",pcmOut->handle); + // prepare PCM for capture and replay + error = AlsaPcmConf(mixer, pcmIn, opts); + if (error) goto OnErrorExit; + // input and output should match - error = AlsaPcmConf(source, pcmOut, opts); + error = AlsaPcmConf(mixer, pcmOut, opts); if (error) goto OnErrorExit; // Prepare PCM for usage if ((error = snd_pcm_prepare(pcmOut->handle)) < 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail to prepare PCM=%s error=%s", ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmCopy: Fail to prepare PCM=%s error=%s", ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); goto OnErrorExit; }; - // prepare PCM for capture and replay - error = AlsaPcmConf(source, pcmIn, opts); - if (error) goto OnErrorExit; - // Prepare PCM for usage if ((error = snd_pcm_start(pcmIn->handle)) < 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail to prepare PCM=%s error=%s", ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmCopy: Fail to prepare PCM=%s error=%s", ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); goto OnErrorExit; }; - AlsaPcmCopyHandleT *pcmCopyHandle = &loopStream->copy; - pcmCopyHandle->info = "pcmCpy"; - pcmCopyHandle->pcmIn = pcmIn->handle; - pcmCopyHandle->pcmOut = pcmOut->handle; - pcmCopyHandle->api = source->api; - pcmCopyHandle->channels = opts->channels; - pcmCopyHandle->frameSize = opts->channels * opts->sampleSize; - pcmCopyHandle->frameCount = ALSA_BUFFER_FRAMES_COUNT; - pcmCopyHandle->buffer = malloc(pcmCopyHandle->frameCount * pcmCopyHandle->frameSize); + AlsaPcmCopyHandleT *cHandle= calloc(1, sizeof(AlsaPcmCopyHandleT)); + cHandle = cHandle; + cHandle->info = "pcmCpy"; + cHandle->pcmIn = pcmIn->handle; + cHandle->pcmOut = pcmOut->handle; + cHandle->api = mixer->api; + cHandle->channels = opts->channels; + cHandle->frameSize = opts->channels * opts->sampleSize; + cHandle->frameCount = ALSA_BUFFER_FRAMES_COUNT; + cHandle->buffer = malloc(cHandle->frameCount * cHandle->frameSize); - // get FD poll descriptor for capture PCM - int pcmInCount = snd_pcm_poll_descriptors_count(pcmCopyHandle->pcmIn); + int pcmInCount = snd_pcm_poll_descriptors_count(cHandle->pcmIn); if (pcmInCount <= 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail pcmIn=%s get fds count error=%s", ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmCopy: Fail pcmIn=%s get fds count error=%s", ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); goto OnErrorExit; }; pcmInFds = alloca(sizeof (*pcmInFds) * pcmInCount); if ((error = snd_pcm_poll_descriptors(pcmIn->handle, pcmInFds, pcmInCount)) < 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail pcmIn=%s get pollfds error=%s", ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmCopy: Fail pcmIn=%s get pollfds error=%s", ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); goto OnErrorExit; }; // add poll descriptor to AGL systemd mainloop - if ((error = sd_event_new(&pcmCopyHandle->sdLoop)) < 0) { + if ((error = sd_event_new(&cHandle->sdLoop)) < 0) { fprintf(stderr, "LaunchCallRequest: fail pcmin=%s creating a new loop: %s\n", ALSA_PCM_UID(pcmOut->handle, string), strerror(error)); goto OnErrorExit; } for (int idx = 0; idx < pcmInCount; idx++) { - if ((error = sd_event_add_io(pcmCopyHandle->sdLoop, &pcmCopyHandle->evtsrc, pcmInFds[idx].fd, EPOLLIN, AlsaPcmReadCB, pcmCopyHandle)) < 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail pcmIn=%s sd_event_add_io err=%d", ALSA_PCM_UID(pcmIn->handle, string), error); + if ((error = sd_event_add_io(cHandle->sdLoop, &cHandle->evtsrc, pcmInFds[idx].fd, EPOLLIN, AlsaPcmReadCB, cHandle)) < 0) { + AFB_ApiError(mixer->api, "AlsaPcmCopy: Fail pcmIn=%s sd_event_add_io err=%d", ALSA_PCM_UID(pcmIn->handle, string), error); goto OnErrorExit; } } // start a thread with a mainloop to monitor Audio-Agent - if ((error = pthread_create(&pcmCopyHandle->thread, NULL, &LoopInThread, pcmCopyHandle)) < 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail create waiting thread pcmIn=%s err=%d", ALSA_PCM_UID(pcmIn->handle, string), error); + if ((error = pthread_create(&cHandle->thread, NULL, &LoopInThread, cHandle)) < 0) { + AFB_ApiError(mixer->api, "AlsaPcmCopy: Fail create waiting thread pcmIn=%s err=%d", ALSA_PCM_UID(pcmIn->handle, string), error); goto OnErrorExit; } // request a higher priority for each audio stream thread struct sched_param params; params.sched_priority = sched_get_priority_max(SCHED_FIFO); - error= pthread_setschedparam(pcmCopyHandle->thread, SCHED_FIFO, ¶ms); + error= pthread_setschedparam(cHandle->thread, SCHED_FIFO, ¶ms); if (error) { - AFB_ApiWarning(source->api, "AlsaPcmCopy: Fail create increase stream thread priority pcmIn=%s err=%s", ALSA_PCM_UID(pcmIn->handle, string), strerror(error)); + AFB_ApiWarning(mixer->api, "AlsaPcmCopy: Fail create increase stream thread priority pcmIn=%s err=%s", ALSA_PCM_UID(pcmIn->handle, string), strerror(error)); } return 0; OnErrorExit: - AFB_ApiError(source->api, "AlsaPcmCopy: - pcmIn=%s" , ALSA_PCM_UID(pcmIn->handle, string)); - AFB_ApiError(source->api, "AlsaPcmCopy: - pcmOut=%s", ALSA_PCM_UID(pcmOut->handle, string)); + AFB_ApiError(mixer->api, "AlsaPcmCopy: - pcmIn=%s" , ALSA_PCM_UID(pcmIn->handle, string)); + AFB_ApiError(mixer->api, "AlsaPcmCopy: - pcmOut=%s", ALSA_PCM_UID(pcmOut->handle, string)); return -1; } diff --git a/plugins/alsa/alsa-effect-ramp.c b/plugins/alsa/alsa-effect-ramp.c index c861d0e..86bdd24 100644 --- a/plugins/alsa/alsa-effect-ramp.c +++ b/plugins/alsa/alsa-effect-ramp.c @@ -24,118 +24,152 @@ for the specific language governing permissions and #define _GNU_SOURCE // needed for vasprintf -#include "alsa-softmixer.h" #include #include +#include "alsa-softmixer.h" + typedef struct { const char *uid; - long current; - long target; + long current; + long target; int numid; - snd_ctl_t *ctlDev; - AlsaVolRampT *params; - sd_event_source *evtsrc; - CtlSourceT *source; + AlsaSndCtlT *sndcard; + AlsaVolRampT *ramp; + sd_event_source *evtsrc; + SoftMixerT *mixer; sd_event *sdLoop; } VolRampHandleT; STATIC int VolRampTimerCB(sd_event_source* source, uint64_t timer, void* handle) { - VolRampHandleT *rampHandle = (VolRampHandleT*) handle; + VolRampHandleT *rHandle = (VolRampHandleT*)handle; int error; uint64_t usec; // RampDown - if (rampHandle->current > rampHandle->target) { - rampHandle->current = rampHandle->current - rampHandle->params->stepDown; - if (rampHandle->current < rampHandle->target) rampHandle->current = rampHandle->target; + if (rHandle->current > rHandle->target) { + rHandle->current = rHandle->current - rHandle->ramp->stepDown; + if (rHandle->current < rHandle->target) rHandle->current = rHandle->target; } // RampUp - if (rampHandle->current < rampHandle->target) { - rampHandle->current = rampHandle->current + rampHandle->params->stepUp; - if (rampHandle->current > rampHandle->target) rampHandle->current = rampHandle->target; + if (rHandle->current < rHandle->target) { + rHandle->current = rHandle->current + rHandle->ramp->stepUp; + if (rHandle->current > rHandle->target) rHandle->current = rHandle->target; } - - error = AlsaCtlNumidSetLong(rampHandle->source, rampHandle->ctlDev, rampHandle->numid, rampHandle->current); + + error = AlsaCtlNumidSetLong(rHandle->mixer, rHandle->sndcard, rHandle->numid, rHandle->current); if (error) goto OnErrorExit; // we reach target stop volram event - if (rampHandle->current == rampHandle->target) { - sd_event_source_unref(rampHandle->evtsrc); - snd_ctl_close (rampHandle->ctlDev); - free (rampHandle); - } - else { + if (rHandle->current == rHandle->target) { + sd_event_source_unref(rHandle->evtsrc); + free(rHandle); + } else { // otherwise validate timer for a new run - sd_event_now(rampHandle->sdLoop, CLOCK_MONOTONIC, &usec); - sd_event_source_set_enabled(rampHandle->evtsrc, SD_EVENT_ONESHOT); - error = sd_event_source_set_time(rampHandle->evtsrc, usec + rampHandle->params->delay); + sd_event_now(rHandle->sdLoop, CLOCK_MONOTONIC, &usec); + sd_event_source_set_enabled(rHandle->evtsrc, SD_EVENT_ONESHOT); + error = sd_event_source_set_time(rHandle->evtsrc, usec + rHandle->ramp->delay); } return 0; OnErrorExit: - AFB_ApiWarning(rampHandle->source->api, "VolRampTimerCB stream=%s numid=%d value=%ld", rampHandle->uid, rampHandle->numid, rampHandle->current); + AFB_ApiWarning(rHandle->mixer->api, "VolRampTimerCB stream=%s numid=%d value=%ld", rHandle->uid, rHandle->numid, rHandle->current); sd_event_source_unref(source); // abandon volRamp return -1; } -PUBLIC int AlsaVolRampApply(CtlSourceT *source, AlsaSndLoopT *frontend, AlsaLoopStreamT *stream, AlsaVolRampT *ramp, json_object *volumeJ) { +PUBLIC int AlsaVolRampApply(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaStreamAudioT *stream, json_object *rampJ) { long curvol, newvol; - const char*volString; - int error; + const char *uid, *volS; + json_object *volJ; + int error, index; uint64_t usec; - - snd_ctl_t *ctlDev =AlsaCtlOpenCtl(source, frontend->cardid); - if (!ctlDev) goto OnErrorExit; - - error = AlsaCtlNumidGetLong(source, ctlDev, stream->volume, &curvol); + + error = wrap_json_unpack(rampJ, "{ss so !}" + , "uid", &uid + , "vol", &volJ + ); if (error) { - AFB_ApiError(source->api,"AlsaVolRampApply:%s(stream) Fail to get volume numid=%d", stream->uid, stream->volume); + AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s invalid-json should {uid:ramp, vol:[+,-,=]value} ramp=%s", mixer->uid, stream->uid, json_object_get_string(rampJ)); goto OnErrorExit; } - switch (json_object_get_type(volumeJ)) { + switch (json_object_get_type(volJ)) { + int count; + case json_type_string: - volString = json_object_get_string(volumeJ); - switch (volString[0]) { + volS = json_object_get_string(volJ); + + switch (volS[0]) { case '+': - sscanf(&volString[1], "%ld", &newvol); + count= sscanf(&volS[1], "%ld", &newvol); newvol = curvol + newvol; break; case '-': - sscanf(&volString[1], "%ld", &newvol); + count= sscanf(&volS[1], "%ld", &newvol); newvol = curvol - newvol; break; + + case '=': + count= sscanf(&volS[1], "%ld", &newvol); + break; + default: - AFB_ApiError(source->api,"AlsaVolRampApply:%s(stream) relative volume should start by '+|-' value=%s", stream->uid, json_object_get_string(volumeJ)); - goto OnErrorExit; + // hope for int as a string and force it as relative + sscanf(&volS[0], "%ld", &newvol); + if (newvol < 0) newvol = curvol - newvol; + else newvol = curvol + newvol; + } + + if (count != 1) { + AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s invalid-numeric expect {uid:%s, vol:[+,-,=]value} get vol:%s", mixer->uid, stream->uid, uid, json_object_get_string(volJ)); + goto OnErrorExit; } break; case json_type_int: - newvol = json_object_get_int(volumeJ); + newvol = json_object_get_int(volJ); break; + default: - AFB_ApiError(source->api,"AlsaVolRampApply:%s(stream) volume should be string or integer value=%s", stream->uid, json_object_get_string(volumeJ)); + AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s invalid-type expect {uid:%s, vol:[+,-,=]value} get vol:%s", mixer->uid, stream->uid, uid, json_object_get_string(volJ)); goto OnErrorExit; } - VolRampHandleT *rampHandle = calloc(1, sizeof (VolRampHandleT)); - rampHandle->uid= stream->uid; - rampHandle->numid= stream->volume; - rampHandle->ctlDev= ctlDev; - rampHandle->source = source; - rampHandle->params = ramp; - rampHandle->target = newvol; - rampHandle->current= curvol; - rampHandle->sdLoop= stream->copy.sdLoop; + error = AlsaCtlNumidGetLong(mixer, sndcard, stream->volume, &curvol); + if (error) { + AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s ramp=%s Fail to get volume from numid=%d", mixer->uid, stream->uid, uid, stream->volume); + goto OnErrorExit; + } + // search for ramp uid in mixer + for (index=0; index<= mixer->max.ramps; index++) { + if (!strcasecmp(mixer->ramps[index]->uid, uid)) { + break; + } + } + + if (index == mixer->max.ramps) { + AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s ramp=%s does not exit", mixer->uid, stream->uid, uid); + goto OnErrorExit; + } + + VolRampHandleT *rHandle = calloc(1, sizeof (VolRampHandleT)); + rHandle->uid = stream->uid; + rHandle->numid = stream->volume; + rHandle->sndcard = sndcard; + rHandle->mixer = mixer; + rHandle->ramp = mixer->ramps[index]; + rHandle->target = newvol; + rHandle->current = curvol; + rHandle->sdLoop = mixer->sdLoop; + // set a timer with ~250us accuracy - sd_event_now(rampHandle->sdLoop, CLOCK_MONOTONIC, &usec); - (void)sd_event_add_time(rampHandle->sdLoop, &rampHandle->evtsrc, CLOCK_MONOTONIC, usec+100, 250, VolRampTimerCB, rampHandle); + sd_event_now(rHandle->sdLoop, CLOCK_MONOTONIC, &usec); + (void) sd_event_add_time(rHandle->sdLoop, &rHandle->evtsrc, CLOCK_MONOTONIC, usec + 100, 250, VolRampTimerCB, rHandle); return 0; diff --git a/plugins/alsa/alsa-plug-dmix.c b/plugins/alsa/alsa-plug-dmix.c index b06d5f6..a9f6b7e 100644 --- a/plugins/alsa/alsa-plug-dmix.c +++ b/plugins/alsa/alsa-plug-dmix.c @@ -25,19 +25,16 @@ static int uniqueIpcIndex = 1024; ALSA_PLUG_PROTO(dmix); -PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave, int open) { +PUBLIC AlsaPcmCtlT* AlsaCreateDmix(SoftMixerT *mixer, const char* pcmName, AlsaSndPcmT *pcmSlave, int open) { snd_config_t *dmixConfig, *slaveConfig, *elemConfig, *pcmConfig; - AlsaPcmInfoT *pcmPlug= calloc(1,sizeof(AlsaPcmInfoT)); - pcmPlug->uid= strdup(pcmName); - pcmPlug->cardid=pcmPlug->uid; - + AlsaPcmCtlT *pcmPlug= calloc(1,sizeof(AlsaPcmCtlT)); + AlsaSndCtlT *sndSlave=pcmSlave->sndcard; + pcmPlug->cid.cardid=pcmName; 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->cardid); + error += snd_config_set_id (dmixConfig, pcmPlug->cid.cardid); error += snd_config_imake_string(&elemConfig, "type", "dmix"); error += snd_config_add(dmixConfig, elemConfig); error += snd_config_imake_integer(&elemConfig, "ipc_key", uniqueIpcIndex++); @@ -45,10 +42,10 @@ 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->cardid); - if (pcmSlave->params.rate) { + error += snd_config_imake_string(&elemConfig, "pcm", sndSlave->cid.cardid); + if (sndSlave->params->rate) { error += snd_config_add(slaveConfig, elemConfig); - error += snd_config_imake_integer(&elemConfig, "rate", pcmSlave->params.rate); + error += snd_config_imake_integer(&elemConfig, "rate", sndSlave->params->rate); } error += snd_config_add(slaveConfig, elemConfig); if (error) goto OnErrorExit; @@ -57,26 +54,28 @@ PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, Als error += snd_config_add(dmixConfig, slaveConfig); if (error) goto OnErrorExit; - if (open) error = _snd_pcm_dmix_open(&pcmPlug->handle, pcmPlug->cardid, snd_config, dmixConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); + if (open) error = _snd_pcm_dmix_open(&pcmPlug->handle, pcmPlug->cid.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 Error=%s", pcmPlug->cardid, pcmSlave->cardid, snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaCreateDmix: fail to create Dmix=%s Slave=%s Error=%s", sndSlave->cid.cardid, sndSlave->cid.cardid, snd_strerror(error)); goto OnErrorExit; } + snd_config_update(); 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->cardid); + AFB_ApiError(mixer->api, "AlsaCreateDmix: fail to add configDMIX=%s", pcmPlug->cid.cardid); goto OnErrorExit; } // Debug config & pcm - //AlsaDumpCtlConfig (source, "plug-dmix", dmixConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateDmix: %s done\n", pcmPlug->cardid); + //AlsaDumpCtlConfig (mixer, "plug-dmix", dmixConfig, 1); + AFB_ApiNotice(mixer->api, "AlsaCreateDmix: %s done\n", pcmPlug->cid.cardid); return pcmPlug; OnErrorExit: - AlsaDumpCtlConfig(source, "plug-dmix", dmixConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateDmix: OnErrorExit\n"); + AlsaDumpCtlConfig(mixer, "plug-pcm", pcmConfig, 1); + AlsaDumpCtlConfig(mixer, "plug-dmix", dmixConfig, 1); + AFB_ApiNotice(mixer->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 deleted file mode 100644 index c28508c..0000000 --- a/plugins/alsa/alsa-plug-multi.c +++ /dev/null @@ -1,106 +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" - -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; - - assert(mixerHandle); - - AlsaPcmInfoT* pcmSlaves=mixerHandle->backend; - if (!pcmSlaves) { - AFB_ApiError(source->api, "AlsaCreateMulti: No Sound Card find [should Registry 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]; - AlsaPcmChannelT *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->uid); - 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(); - - if (open) 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); - 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-rate.c b/plugins/alsa/alsa-plug-rate.c index a9b9e23..4c83c7f 100644 --- a/plugins/alsa/alsa-plug-rate.c +++ b/plugins/alsa/alsa-plug-rate.c @@ -23,59 +23,60 @@ ALSA_PLUG_PROTO(rate); +PUBLIC AlsaPcmCtlT* AlsaCreateRate(SoftMixerT *mixer, const char* pcmName, AlsaPcmCtlT *pcmSlave, AlsaPcmHwInfoT *params, int open) { -PUBLIC AlsaPcmInfoT* AlsaCreateRate(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave, int open) { - snd_config_t *rateConfig, *slaveConfig, *elemConfig, *pcmConfig; - AlsaPcmInfoT *pcmPlug= calloc(1,sizeof(AlsaPcmInfoT)); - pcmPlug->uid= strdup(pcmName); - pcmPlug->cardid=pcmPlug->uid; + AlsaPcmCtlT *pcmPlug = calloc(1, sizeof (AlsaPcmCtlT)); + pcmPlug->cid.cardid = pcmName; - int error=0; + int error = 0; // refresh global alsalib config and create PCM top config snd_config_update(); error += snd_config_top(&rateConfig); - error += snd_config_set_id (rateConfig, pcmPlug->cardid); - error += snd_config_imake_string(&elemConfig, "type", "plug"); + error += snd_config_set_id(rateConfig, pcmPlug->cid.cardid); + error += snd_config_imake_string(&elemConfig, "type", "rate"); error += snd_config_add(rateConfig, elemConfig); if (error) goto OnErrorExit; error += snd_config_make_compound(&slaveConfig, "slave", 0); - error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->cardid); - if (pcmSlave->params.rate) { + error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->cid.cardid); + error += snd_config_add(slaveConfig, elemConfig); + if (params->rate) { + error += snd_config_imake_integer(&elemConfig, "rate", params->rate); + error += snd_config_add(slaveConfig, elemConfig); + } + if (params->format) { + error += snd_config_imake_string(&elemConfig, "format", params->formatS); error += snd_config_add(slaveConfig, elemConfig); - // *** error += snd_config_imake_integer(&elemConfig, "rate", pcmSlave->params.rate); - error += snd_config_imake_integer(&elemConfig, "rate", 48000); } - error += snd_config_add(slaveConfig, elemConfig); if (error) goto OnErrorExit; // add leaf into config error += snd_config_add(rateConfig, slaveConfig); if (error) goto OnErrorExit; - - error += snd_config_search(snd_config, "pcm", &pcmConfig); + + error += snd_config_search(snd_config, "pcm", &pcmConfig); error += snd_config_add(pcmConfig, rateConfig); if (error) { - AFB_ApiError(source->api, "AlsaCreateRate: fail to add configRATE=%s", pcmPlug->cardid); + AFB_ApiError(mixer->api, "AlsaCreateRate: fail to add configRATE=%s", pcmPlug->cid.cardid); goto OnErrorExit; } - - if (open) error = _snd_pcm_rate_open(&pcmPlug->handle, pcmPlug->cardid, snd_config, rateConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); + + if (open) error = _snd_pcm_rate_open(&pcmPlug->handle, pcmPlug->cid.cardid, snd_config, rateConfig, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (error) { - AFB_ApiError(source->api, "AlsaCreateRate: fail to create Rate=%s Slave=%s Error=%s", pcmPlug->cardid, pcmSlave->cardid, snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaCreateRate: fail to create Rate=%s Slave=%s Error=%s", pcmPlug->cid.cardid, pcmSlave->cid.cardid, snd_strerror(error)); goto OnErrorExit; } - + // Debug config & pcm - AlsaDumpCtlConfig (source, "plug-rate", pcmConfig, 1); - //AlsaDumpCtlConfig (source, "plug-rate", rateConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateRate: %s done\n", pcmPlug->cardid); + AlsaDumpCtlConfig(mixer, "plug-rate", pcmConfig, 1); + //AlsaDumpCtlConfig (mixer, "plug-rate", rateConfig, 1); + AFB_ApiNotice(mixer->api, "AlsaCreateRate: %s done\n", pcmPlug->cid.cardid); return pcmPlug; OnErrorExit: - AlsaDumpCtlConfig(source, "plug-rate", rateConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateRate: OnErrorExit\n"); + AlsaDumpCtlConfig(mixer, "plug-rate", rateConfig, 1); + AFB_ApiNotice(mixer->api, "AlsaCreateRate: 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 index f254c68..ac3aa1c 100644 --- a/plugins/alsa/alsa-plug-route.c +++ b/plugins/alsa/alsa-plug-route.c @@ -22,122 +22,163 @@ ALSA_PLUG_PROTO(route); -STATIC int CardChannelByUid(CtlSourceT *source, AlsaPcmInfoT *pcmBackend, const char *uid) { - int channelIdx = -1; +typedef struct { + const char *uid; + const char *cardid; + int cardidx; + int ccount; + int port; +} ChannelCardPortT; - // 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++) { - AlsaPcmChannelT *channels = pcmBackend[cardIdx].channels; +STATIC int CardChannelByUid(SoftMixerT *mixer, const char *uid, ChannelCardPortT *response) { + + // search for channel within all sound card sink (channel port target is computed by order) + for (int idx = 0; mixer->sinks[idx]; idx++) { + int jdx; + + AlsaPcmChannelT **channels = mixer->sinks[idx]->channels; if (!channels) { - AFB_ApiError(source->api, "CardChannelByUid: No Backend card=%s [should declare channels]", pcmBackend[cardIdx].uid); + AFB_ApiError(mixer->api, "CardChannelByUid: No Sink card=%s [should declare channels]", mixer->sinks[idx]->uid); goto OnErrorExit; } - for (int idx = 0; channels[idx].uid != NULL; idx++) { - if (!strcmp(channels[idx].uid, uid)) return targetIdx; - targetIdx++; + for (jdx = 0; jdx < mixer->sinks[idx]->ccount; jdx++) { + if (!strcasecmp(channels[jdx]->uid, uid)) { + response->port = channels[jdx]->port; + response->uid = mixer->sinks[idx]->uid; + response->ccount = mixer->sinks[idx]->ccount; + response->cardid = mixer->sinks[idx]->sndcard->cid.cardid; + response->cardidx = mixer->sinks[idx]->sndcard->cid.cardidx; + break; + } + } + + if (jdx == mixer->sinks[idx]->ccount) { + AFB_ApiError(mixer->api, "CardChannelByUid: No Channel with uid=%s [should declare channels]", uid); + goto OnErrorExit; } } - // this is OnErrorExit - return channelIdx; + return 0; OnErrorExit: return -1; } -PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone, int open) { - SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; +PUBLIC AlsaPcmCtlT* AlsaCreateRoute(SoftMixerT *mixer, AlsaSndZoneT *zone, int open) { 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); + int scount=0, error = 0; + ChannelCardPortT slave, channel; + AlsaPcmCtlT *pcmRoute = calloc(1, sizeof (AlsaPcmCtlT)); - AlsaPcmInfoT *pcmBackend = mixerHandle->backend; - AlsaPcmInfoT* pcmSlave=mixerHandle->multiPcm; - if (!pcmBackend || !pcmSlave) { - AFB_ApiError(source->api, "AlsaCreateRoute: mixer=%s zone(%s)(zone) No Sound Card Ctl find [should Registry snd_cards first]" - , mixerHandle->uid, zone->uid); + if (!mixer->sinks) { + AFB_ApiError(mixer->api, "AlsaCreateRoute: mixer=%s zone(%s)(zone) No sink found [should Registry sound card first]", mixer->uid, 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; + char *cardid; + (void) asprintf(&cardid, "route-%s", zone->uid); + pcmRoute->cid.cardid = (const char *) cardid; + + pcmRoute->params = ApiSinkGetParamsByZone(mixer, zone->uid); + zone->params=pcmRoute->params; + + // use 1st zone channel to retrieve sound card name + channel count. + error = CardChannelByUid(mixer, zone->sinks[0]->uid, &slave); + if (error) { + AFB_ApiError(mixer->api, "AlsaCreateRoute:zone(%s) fail to find channel=%s", zone->uid, zone->sinks[0]->uid); + goto OnErrorExit; + } + + // move from hardware to DMIX attach to sndcard + char *dmixUid; + (void) asprintf(&dmixUid, "dmix-%s", slave.uid); + + // temporary store to unable multiple channel to route to the same port + snd_config_t **cports = alloca(slave.ccount * sizeof (void*)); + memset(cports, 0, slave.ccount * sizeof (void*)); + int zcount = 0; - // 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))); + // We create 1st ttable to retrieve sndcard slave and channel count + (void)snd_config_make_compound(&tableConfig, "ttable", 0); - // loop on sound card to include into multi - for (int idx = 0; zone->channels[idx].uid != NULL; idx++) { + for (scount = 0; zone->sinks[scount] != NULL; scount++) { + + error = CardChannelByUid(mixer, zone->sinks[scount]->uid, &channel); + if (error) { + AFB_ApiError(mixer->api, "AlsaCreateRoute:zone(%s) fail to find channel=%s", zone->uid, zone->sinks[scount]->uid); + goto OnErrorExit; + } - 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); + if (slave.cardidx != channel.cardidx) { + AFB_ApiError(mixer->api, "AlsaCreateRoute:zone(%s) cannot span over multiple sound card %s != %s ", zone->uid, slave.cardid, channel.cardid); goto OnErrorExit; } - - int channel = zone->channels[idx].port; + + int port = zone->sinks[scount]->port; + int target = channel.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++; + if (!cports[port]) { + zcount++; 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]); + snprintf(channelS, sizeof (channelS), "%d", port); + error += snd_config_make_compound(&cports[port], channelS, 0); + error += snd_config_add(tableConfig, cports[port]); } // 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); + error += snd_config_add(cports[port], elemConfig); if (error) goto OnErrorExit; } + if (error) goto OnErrorExit; - // update top config to access previous plugin PCM + // update zone with route channel count and sndcard params + pcmRoute->ccount = zcount; + zone->ccount=zcount; + + // refresh global alsalib config and create PCM top config snd_config_update(); + error += snd_config_top(&routeConfig); + error += snd_config_set_id(routeConfig, 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", dmixUid); + error += snd_config_add(slaveConfig, elemConfig); +// error += snd_config_imake_integer(&elemConfig, "channels", scount); +// error += snd_config_add(slaveConfig, elemConfig); + error += snd_config_add(routeConfig, slaveConfig); + error += snd_config_add(routeConfig, tableConfig); + if (error) goto OnErrorExit; - if (open) error = _snd_pcm_route_open(&pcmPlug->handle, zone->uid, snd_config, routeConfig, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (open) error = _snd_pcm_route_open(&pcmRoute->handle, pcmRoute->cid.cardid, 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)); + AFB_ApiError(mixer->api, "AlsaCreateRoute:zone(%s) fail to create Plug=%s error=%s", zone->uid, pcmRoute->cid.cardid, snd_strerror(error)); goto OnErrorExit; } + snd_config_update(); 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); + AFB_ApiError(mixer->api, "AlsaCreateDmix:%s fail to add config route=%s error=%s", zone->uid, pcmRoute->cid.cardid, snd_strerror(error)); goto OnErrorExit; } // Debug config & pcm - //AlsaDumpCtlConfig(source, "plug-route", routeConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateRoute:zone(%s) done\n", zone->uid); - return pcmPlug; + AFB_ApiNotice(mixer->api, "AlsaCreateRoute:zone(%s) done", zone->uid); + AlsaDumpCtlConfig(mixer, "plug-route", routeConfig, 1); + return pcmRoute; OnErrorExit: - AlsaDumpCtlConfig(source, "plug-route", routeConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateRoute:zone(%s) OnErrorExit\n", zone->uid); + free(pcmRoute); + AlsaDumpCtlConfig(mixer, "plug-pcm", snd_config, 1); + AlsaDumpCtlConfig(mixer, "plug-route", routeConfig, 1); + AFB_ApiNotice(mixer->api, "AlsaCreateRoute:zone(%s) OnErrorExit\n", zone->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 index cb764ef..f16b0a9 100644 --- a/plugins/alsa/alsa-plug-vol.c +++ b/plugins/alsa/alsa-plug-vol.c @@ -22,61 +22,30 @@ ALSA_PLUG_PROTO(softvol); // stream uses solftvol plugin -STATIC AlsaPcmInfoT* SlaveZoneByUid(CtlSourceT *source, AlsaPcmInfoT **pcmZones, const char *uid) { - AlsaPcmInfoT *slaveZone= NULL; - - // Loop on every Registryed 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* AlsaCreateSoftvol(CtlSourceT *source, AlsaLoopStreamT *stream, AlsaPcmInfoT *ctlControl, const char* ctlName, int max, int open) { - SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; +PUBLIC AlsaPcmCtlT* AlsaCreateSoftvol(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaSndZoneT *zone, AlsaSndCtlT *sndcard, char* ctlName, int max, int open) { snd_config_t *streamConfig, *elemConfig, *slaveConfig, *controlConfig,*pcmConfig; + AlsaPcmCtlT *pcmVol= calloc(1,sizeof(AlsaPcmCtlT)); int error = 0; - AlsaPcmInfoT *pcmPlug= calloc(1,sizeof(AlsaPcmInfoT)); - - assert (mixerHandle); - - // assert static/global softmixer handle get requited info - AlsaSndLoopT *ctlLoop = mixerHandle->frontend; - if (!ctlLoop) { - AFB_ApiError(source->api, "AlsaCreateSoftvol:%s(stream) No Loop found [should Registry snd_loop first]",stream->uid); - goto OnErrorExit; - } // assert static/global softmixer handle get requited info - AlsaPcmInfoT **pcmZones = mixerHandle->routes; + AlsaSndZoneT **pcmZones = mixer->zones; if (!pcmZones) { - AFB_ApiError(source->api, "AlsaCreateSoftvol:%s(stream) No Zone found [should Registry snd_zones first]", stream->uid); + AFB_ApiError(mixer->api, "AlsaCreateSoftvol:%s(stream) No Zone found [should Registry zones first]", stream->uid); goto OnErrorExit; } + + char *cardid; + (void) asprintf(&cardid, "softvol-%s", stream->uid); + pcmVol->cid.cardid = (const char *) cardid; - // search for target zone uid - AlsaPcmInfoT *pcmSlave= SlaveZoneByUid (source, pcmZones, stream->zone); - if (!pcmSlave || !pcmSlave->uid) { - AFB_ApiError(source->api, "AlsaCreateSoftvol:%s(stream) fail to find Zone=%s", stream->uid, stream->zone); - goto OnErrorExit; - } + char *slaveid; + (void)asprintf(&slaveid, "route-%s", zone->uid); + // Fulup debug (void) asprintf(&slaveid, "dmix-%s", "8CH-USB"); - // stream inherit from zone channel count - pcmPlug->uid= strdup(stream->uid); - pcmPlug->cardid= pcmPlug->uid; - pcmPlug->devpath=NULL; - pcmPlug->ccount= pcmSlave->ccount; - memcpy (&pcmPlug->params, &stream->params, sizeof(AlsaPcmHwInfoT)); - pcmPlug->params.channels= 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_set_id (streamConfig, pcmVol->cid.cardid); error += snd_config_imake_string(&elemConfig, "type", "softvol"); error += snd_config_add(streamConfig, elemConfig); error += snd_config_imake_integer(&elemConfig, "resolution", max+1); @@ -85,7 +54,7 @@ PUBLIC AlsaPcmInfoT* AlsaCreateSoftvol(CtlSourceT *source, AlsaLoopStreamT *stre // add slave leaf error += snd_config_make_compound(&slaveConfig, "slave", 0); - error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->cardid); + error += snd_config_imake_string(&elemConfig, "pcm", slaveid); error += snd_config_add(slaveConfig, elemConfig); error += snd_config_add(streamConfig, slaveConfig); if (error) goto OnErrorExit; @@ -94,34 +63,36 @@ PUBLIC AlsaPcmInfoT* AlsaCreateSoftvol(CtlSourceT *source, AlsaLoopStreamT *stre error += snd_config_make_compound(&controlConfig, "control", 0); error += snd_config_imake_string(&elemConfig, "name", ctlName); error += snd_config_add(controlConfig, elemConfig); - error += snd_config_imake_integer(&elemConfig, "card", ctlControl->cardidx); + error += snd_config_imake_integer(&elemConfig, "card", sndcard->cid.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(); - - if (open) error = _snd_pcm_softvol_open(&pcmPlug->handle, stream->uid, snd_config, streamConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); + if (open) error = _snd_pcm_softvol_open(&pcmVol->handle, stream->uid, snd_config, streamConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); if (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)); + AFB_ApiError(mixer->api, "AlsaCreateSoftvol:%s(stream) fail to create Plug=%s Slave=%s error=%s", stream->uid, pcmVol->cid.cardid, sndcard->cid.cardid, snd_strerror(error)); goto OnErrorExit; } + // update top config to access previous plugin PCM + snd_config_update(); + error += snd_config_search(snd_config, "pcm", &pcmConfig); error += snd_config_add(pcmConfig, streamConfig); if (error) { - AFB_ApiError(source->api, "AlsaCreateSoftvol:%s(stream) fail to add config", stream->uid); + AFB_ApiError(mixer->api, "AlsaCreateSoftvol:%s(stream) fail to add config error=%s", stream->uid, snd_strerror(error)); goto OnErrorExit; } // Debug config & pcm - //AlsaDumpCtlConfig (source, "plug-stream", streamConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateSoftvol:%s(stream) done\n", stream->uid); - return pcmPlug; + //AlsaDumpCtlConfig (mixer, "plug-config", pcmConfig, 1); + //AlsaDumpCtlConfig(mixer, "plug-softvol", streamConfig, 1); + AFB_ApiNotice(mixer->api, "AlsaCreateSoftvol:%s(stream) done\n", stream->uid); + return pcmVol; OnErrorExit: - AlsaDumpCtlConfig(source, "plug-stream", streamConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateSoftvol:%s(stream) OnErrorExit\n", stream->uid); + AlsaDumpCtlConfig (mixer, "plug-config", pcmConfig, 1); + AlsaDumpCtlConfig(mixer, "plug-softvol", streamConfig, 1); + AFB_ApiNotice(mixer->api, "AlsaCreateSoftvol:%s(stream) OnErrorExit\n", stream->uid); return NULL; } \ No newline at end of file diff --git a/plugins/alsa/alsa-softmixer.h b/plugins/alsa/alsa-softmixer.h index a8d7af6..f8d94e4 100644 --- a/plugins/alsa/alsa-softmixer.h +++ b/plugins/alsa/alsa-softmixer.h @@ -40,14 +40,21 @@ #define STATIC static #endif +#define MAINLOOP_CONCURENCY 0 #define MAINLOOP_WATCHDOG 30000 -#define MAX_AUDIO_STREAMS 8*2 #define ALSA_DEFAULT_PCM_RATE 48000 #define ALSA_DEFAULT_PCM_VOLUME 80 #define ALSA_BUFFER_FRAMES_COUNT 1024 #define ALSA_CARDID_MAX_LEN 64 +#define SMIXER_SUBDS_CTLS 3 +#define SMIXER_DEFLT_LOOPS 4 +#define SMIXER_DEFLT_SINKS 8 +#define SMIXER_DEFLT_SOURCES 32 +#define SMIXER_DEFLT_ZONES 32 +#define SMIXER_DEFLT_STREAMS 32 +#define SMIXER_DEFLT_RAMPS 8 #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) @@ -59,7 +66,6 @@ #define STATIC static #endif - typedef enum { FONTEND_NUMID_IGNORE, FONTEND_NUMID_PAUSE, @@ -82,11 +88,13 @@ typedef struct { } AlsaPcmCopyHandleT; typedef struct { - const char *uid; - int delay; // delay between volset in us - int stepDown; // linear % - int stepUp; // linear % -} AlsaVolRampT; + const char*name; + int numid; + int count; + long min; + long max; + long step; +} AlsaSndControlT; typedef struct { const char*uid; @@ -96,6 +104,7 @@ typedef struct { typedef struct { unsigned int rate; unsigned int channels; + const char *formatS; snd_pcm_format_t format; snd_pcm_access_t access; size_t sampleSize; @@ -103,126 +112,175 @@ typedef struct { typedef struct { const char *uid; + int delay; // delay between volset in us + int stepDown; // linear % + int stepUp; // linear % +} AlsaVolRampT; + +typedef struct { + int cardidx; const char *devpath; const char *cardid; - int cardidx; + const char *name; + const char *longname; int device; int subdev; - int numid; +} AlsaDevInfoT; + +typedef struct { int ccount; + AlsaDevInfoT cid; snd_pcm_t *handle; - AlsaPcmChannelT *channels; - AlsaPcmHwInfoT params; -} AlsaPcmInfoT; + AlsaPcmHwInfoT *params; +} AlsaPcmCtlT; typedef struct { - const char *uid; - snd_pcm_stream_t type; - AlsaPcmChannelT *channels; - AlsaPcmInfoT *pcm; -} AlsaSndZoneT; + int numid; + RegistryNumidT type; + AlsaPcmCtlT *pcm; +} RegistryEntryPcmT; + +typedef struct { + long rcount; + AlsaDevInfoT cid; + snd_ctl_t *ctl; + AlsaPcmHwInfoT *params; + RegistryEntryPcmT **registry; +} AlsaSndCtlT; typedef struct { - AlsaPcmInfoT *pcm; - int numid; - RegistryNumidT type; -} RegistryStreamT; + const char *uid; + AlsaPcmChannelT **sources; + AlsaPcmChannelT **sinks; + int ccount; + AlsaPcmHwInfoT *params; +} AlsaSndZoneT; typedef struct { - RegistryStreamT stream[MAX_AUDIO_STREAMS + 1]; - int count; - snd_ctl_t *ctlDev; -} RegistryHandleT; + const char *uid; + unsigned int ccount; + AlsaSndCtlT *sndcard; + AlsaSndControlT volume; + AlsaSndControlT mute; + AlsaPcmChannelT **channels; + snd_pcm_stream_t direction; +} AlsaSndPcmT; +typedef struct { + const char*uid; + int index; + int numid; +} AlsaLoopSubdevT; typedef struct { const char *uid; - const char *devpath; - const char *cardid; - int cardidx; int playback; int capture; - int scount; - AlsaPcmInfoT *subdevs; - AlsaVolRampT *ramps; - RegistryHandleT *registry; + long scount; + AlsaSndCtlT *sndcard; + AlsaLoopSubdevT **subdevs; } AlsaSndLoopT; -typedef struct { +typedef struct { const char *uid; const char *info; - const char *zone; + const char *sink; + const char *source; const char *ramp; int volume; int mute; - AlsaPcmInfoT *pcm; - AlsaPcmHwInfoT params; - AlsaPcmCopyHandleT copy; -} AlsaLoopStreamT; + AlsaPcmHwInfoT *params; + AlsaPcmCopyHandleT *copy; +} AlsaStreamAudioT; typedef struct { const char *uid; const char *info; - AlsaSndLoopT *frontend; - AlsaPcmInfoT *backend; - AlsaPcmInfoT *multiPcm; - AlsaPcmInfoT **routes; - AlsaLoopStreamT *streams; -} SoftMixerHandleT; + AFB_ApiT api; + sd_event *sdLoop; + + struct { + unsigned int loops; + unsigned int sinks; + unsigned int sources; + unsigned int zones; + unsigned int streams; + unsigned int ramps; + } max; + AlsaSndLoopT **loops; + AlsaSndPcmT **sinks; + AlsaSndPcmT **sources; + AlsaSndZoneT **zones; + AlsaStreamAudioT **streams; + AlsaVolRampT **ramps; +} SoftMixerT; // 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); +PUBLIC snd_ctl_card_info_t *AlsaByPathInfo(SoftMixerT *mixer, const char *devpath); +PUBLIC AlsaPcmCtlT *AlsaByPathOpenPcm(SoftMixerT *mixer, AlsaDevInfoT *pcmId, snd_pcm_stream_t direction); +PUBLIC snd_ctl_t *AlsaByPathOpenCtl(SoftMixerT *mixer, const char *uid, AlsaSndCtlT *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 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, 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)) +PUBLIC json_object *AlsaDumpObjF(const char *format, ...); +PUBLIC char *AlsaDumpPcmUid(snd_pcm_t *pcmHandle, char *buffer, size_t len); +PUBLIC char *AlsaDumpCtlUid(snd_ctl_t *ctlHandle, char *buffer, size_t len); +PUBLIC void AlsaDumpFormats(SoftMixerT *mixer, snd_pcm_t *pcmHandle); +PUBLIC void AlsaDumpCtlSubdev(SoftMixerT *mixer, snd_ctl_t *handle); +PUBLIC void AlsaDumpPcmParams(SoftMixerT *mixer, snd_pcm_hw_params_t *pcmHwParams); +PUBLIC void AlsaDumpPcmInfo(SoftMixerT *mixer, const char* info, snd_pcm_t *pcm); +PUBLIC void AlsaDumpElemConfig(SoftMixerT *mixer, const char* info, const char* elem); +PUBLIC void AlsaDumpCtlConfig(SoftMixerT *mixer, const char* info, snd_config_t *config, int indent); // alsa-core-ctl.c -PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(CtlSourceT *source, const char *cardid); -PUBLIC snd_ctl_t *AlsaCtlOpenCtl(CtlSourceT *source, const char *cardid); -PUBLIC snd_ctl_t* AlsaCrlFromPcm(CtlSourceT *source, snd_pcm_t *pcm); -PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t * ctlDev, RegistryHandleT *registry); -PUBLIC int AlsaCtlRegister(CtlSourceT *source, SoftMixerHandleT *mixer, AlsaPcmInfoT *pcm, RegistryNumidT type, int numid); -PUBLIC int AlsaCtlNumidGetLong(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long* value); -PUBLIC int AlsaCtlNumidSetLong(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long value); -PUBLIC int AlsaCtlNameGetLong(CtlSourceT *source, snd_ctl_t* ctlDev, const char *ctlName, long* value); -PUBLIC int AlsaCtlNameSetLong(CtlSourceT *source, snd_ctl_t* ctlDev, const char *ctlName, long value); -PUBLIC int AlsaCtlCreateControl(CtlSourceT *source, snd_ctl_t* ctlDev, AlsaPcmInfoT *subdevs, char* name, int ctlCount, int ctlMin, int ctlMax, int ctlStep, long value); -PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(CtlSourceT *source, snd_ctl_t* ctlDev, const char *ctlName); -PUBLIC int CtlElemIdSetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, long value); -PUBLIC int CtlElemIdGetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, long *value); +PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNumidElemId(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numid) ; +PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName) ; +PUBLIC snd_ctl_t *AlsaCtlOpenCtl(SoftMixerT *mixer, const char *cardid) ; +PUBLIC int CtlElemIdGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, snd_ctl_elem_id_t *elemId, long *value) ; +PUBLIC int CtlElemIdSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, snd_ctl_elem_id_t *elemId, long value) ; +PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(SoftMixerT *mixer, const char *cardid) ; +PUBLIC int AlsaCtlNumidSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numid, long value) ; +PUBLIC int AlsaCtlNumidGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numid, long* value) ; +PUBLIC int AlsaCtlNameSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName, long value) ; +PUBLIC int AlsaCtlNameGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName, long* value) ; +PUBLIC int AlsaCtlCreateControl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, char* ctlName, int ctlCount, int ctlMin, int ctlMax, int ctlStep, long value) ; +PUBLIC snd_ctl_t* AlsaCrlFromPcm(SoftMixerT *mixer, snd_pcm_t *pcm) ; +PUBLIC int AlsaCtlSubscribe(SoftMixerT *mixer, const char *uid, AlsaSndCtlT *sndcard) ; +PUBLIC int AlsaCtlRegister(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaPcmCtlT *pcmdev, RegistryNumidT type, int numid); // alsa-core-pcm.c -PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *opts); -PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaLoopStreamT *loopStream, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pcmOut, AlsaPcmHwInfoT * opts); +PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, AlsaPcmHwInfoT *opts); +PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT *pcmIn, AlsaPcmCtlT *pcmOut, AlsaPcmHwInfoT * opts); + // alsa-plug-*.c _snd_pcm_PLUGIN_open_ see macro ALSA_PLUG_PROTO(plugin) -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* AlsaCreateSoftvol(CtlSourceT *source, AlsaLoopStreamT *stream, AlsaPcmInfoT *ctlControl, const char* ctlName, int max, int open); -PUBLIC AlsaPcmInfoT* AlsaCreateRate(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave, int open); +PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, AlsaPcmHwInfoT *opts); +PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *streamAudio, AlsaPcmCtlT *pcmIn, AlsaPcmCtlT *pcmOut, AlsaPcmHwInfoT * opts); +PUBLIC AlsaPcmCtlT* AlsaCreateSoftvol(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaSndZoneT *zone, AlsaSndCtlT *sndcard, char* ctlName, int max, int open); +PUBLIC AlsaPcmCtlT* AlsaCreateRoute(SoftMixerT *mixer, AlsaSndZoneT *zone, int open); +PUBLIC AlsaPcmCtlT* AlsaCreateRate(SoftMixerT *mixer, const char* pcmName, AlsaPcmCtlT *pcmSlave, AlsaPcmHwInfoT *params, int open); +PUBLIC AlsaPcmCtlT* AlsaCreateDmix(SoftMixerT *mixer, const char* pcmName, AlsaSndPcmT *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 LoopStreams(CtlSourceT *source, json_object *argsJ, json_object **responseJ); +PUBLIC AlsaLoopSubdevT *ApiLoopFindSubdev(SoftMixerT *mixer, const char *streamUid, const char *targetUid, AlsaSndLoopT **loop); +PUBLIC int ApiLoopAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ); +PUBLIC AlsaPcmHwInfoT *ApiPcmSetParams(SoftMixerT *mixer, const char *uid, json_object *paramsJ); +PUBLIC AlsaSndPcmT *ApiPcmAttachOne(SoftMixerT *mixer, const char *uid, snd_pcm_stream_t direction, json_object *argsJ); +PUBLIC AlsaVolRampT *ApiRampGetByUid(SoftMixerT *mixer, const char *uid); +PUBLIC int ApiRampAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object *argsJ); +PUBLIC AlsaPcmHwInfoT *ApiSinkGetParamsByZone(SoftMixerT *mixer, const char *target); +PUBLIC int ApiSinkAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ); +PUBLIC AlsaSndCtlT *ApiSourceFindSubdev(SoftMixerT *mixer, const char *target); +PUBLIC int ApiSourceAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ); +PUBLIC json_object *CreateOneStream(SoftMixerT *mixer, AlsaStreamAudioT *stream); +PUBLIC int ApiStreamAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ, json_object **responseJ); +PUBLIC AlsaSndZoneT *ApiZoneGetByUid(SoftMixerT *mixer, const char *target); +PUBLIC int ApiZoneAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ); // alsa-effect-ramp.c -PUBLIC int AlsaVolRampApply(CtlSourceT *source, AlsaSndLoopT *frontend, AlsaLoopStreamT *stream, AlsaVolRampT *ramp, json_object *volumeJ); +PUBLIC AlsaVolRampT *ApiRampGetByUid(SoftMixerT *mixer, const char *uid); +PUBLIC int AlsaVolRampApply(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaStreamAudioT *stream, json_object *rampJ); #endif \ No newline at end of file diff --git a/plugins/alsa/alsa-utils-bypath.c b/plugins/alsa/alsa-utils-bypath.c index a857ab1..30e528f 100644 --- a/plugins/alsa/alsa-utils-bypath.c +++ b/plugins/alsa/alsa-utils-bypath.c @@ -35,7 +35,7 @@ // Clone of AlsaLib snd_card_load2 static function -PUBLIC snd_ctl_card_info_t *AlsaByPathInfo(CtlSourceT *source, const char *devpath) { +PUBLIC snd_ctl_card_info_t *AlsaByPathInfo(SoftMixerT *mixer, const char *devpath) { int open_dev; snd_ctl_card_info_t *cardInfo = malloc(snd_ctl_card_info_sizeof()); @@ -54,104 +54,72 @@ PUBLIC snd_ctl_card_info_t *AlsaByPathInfo(CtlSourceT *source, const char *devpa return cardInfo; OnErrorExit: - AFB_ApiError(source->api, "AlsaCardInfoByPath: fail to find sndcard by path= %s", devpath); + AFB_ApiError(mixer->api, "AlsaCardInfoByPath: fail to find sndcard by path= %s", devpath); return NULL; } -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); - 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); - } - - if (!cardInfo) { - 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->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); +PUBLIC AlsaPcmCtlT *AlsaByPathOpenPcm(SoftMixerT *mixer, AlsaDevInfoT *pcmDev, snd_pcm_stream_t direction) { + int error; + AlsaPcmCtlT *pcmCtl = calloc(1, sizeof (AlsaPcmCtlT)); + + if (!pcmDev->cardid) { + char *cardid; + if (pcmDev->subdev) (void)asprintf(&cardid, "hw:%i,%i,%i", pcmDev->cardidx, pcmDev->device, pcmDev->subdev); + else if (pcmDev->device) (void) asprintf(&cardid, "hw:%i,%i", pcmDev->cardidx, pcmDev->device); + else (void) asprintf(&cardid, "hw:%i", pcmDev->cardidx); + pcmDev->cardid= (const char*)cardid; } - - // make sure UID will cannot be removed - dev->uid= strdup(dev->uid); - return 0; -OnErrorExit: - return -1; -} + // inherit CID fropm pcmDev + pcmCtl->cid.cardid = pcmDev->cardid; + pcmCtl->cid.cardidx = pcmDev->cardidx; + pcmCtl->cid.device = pcmDev->device; + pcmCtl->cid.subdev = pcmDev->subdev; + pcmCtl->cid.name=NULL; + pcmCtl->cid.longname=NULL; -PUBLIC AlsaPcmInfoT* AlsaByPathOpenPcm(CtlSourceT *source, AlsaPcmInfoT *dev, snd_pcm_stream_t direction) { - int error; - - // 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(&pcm->handle, pcm->cardid, direction, SND_PCM_NONBLOCK); + error = snd_pcm_open(&pcmCtl->handle, pcmCtl->cid.cardid, direction, SND_PCM_NONBLOCK); if (error) { - AFB_ApiError(source->api, "AlsaByPathOpenPcm: fail openpcm (cardid=%s idxdev=%i subdev=%d): %s" - , pcm->cardid, pcm->device, pcm->subdev, snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaByPathOpenPcm: fail openpcm cardid=%s error=%s", pcmCtl->cid.cardid, snd_strerror(error)); goto OnErrorExit; } - return (pcm); + return pcmCtl; OnErrorExit: + free(pcmCtl); return NULL; } -PUBLIC snd_ctl_t *AlsaByPathOpenCtl(CtlSourceT *source, AlsaPcmInfoT *dev) { +PUBLIC snd_ctl_t *AlsaByPathOpenCtl(SoftMixerT *mixer, const char *uid, AlsaSndCtlT *dev) { int err; - 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->cardid) cardInfo = AlsaCtlGetInfo(source, dev->cardid); + if (dev->cid.devpath) cardInfo = AlsaByPathInfo(mixer, dev->cid.devpath); + else if (dev->cid.cardid) cardInfo = AlsaCtlGetInfo(mixer, dev->cid.cardid); if (!cardInfo) { - AFB_ApiError(source->api, "AlsaByPathOpenCtl: fail to find sndcard by path=%s id=%s", dev->devpath, dev->cardid); + AFB_ApiError(mixer->api, "AlsaByPathOpenCtl: uid=%s fail to find sndcard by path=%s id=%s", uid, dev->cid.devpath, dev->cid.cardid); goto OnErrorExit; } // extract useful info from cardInfo handle - int cardIndex = snd_ctl_card_info_get_card(cardInfo); - const char *cardId = snd_ctl_card_info_get_id(cardInfo); - const char *cardName = snd_ctl_card_info_get_name(cardInfo); + dev->cid.devpath = NULL; + dev->cid.cardidx = snd_ctl_card_info_get_card(cardInfo); + dev->cid.name = strdup(snd_ctl_card_info_get_name(cardInfo)); + dev->cid.longname = strdup(snd_ctl_card_info_get_longname(cardInfo)); // build a valid name and open sndcard - 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)); + (void) asprintf((char**) &dev->cid.cardid, "hw:%i", dev->cid.cardidx); + + if ((err = snd_ctl_open(&handle, dev->cid.cardid, 0)) < 0) { + AFB_ApiError(mixer->api, "AlsaByPathOpenCtl uid=%s sndcard open fail cardid=%s longname=%s error=%s", uid, dev->cid.cardid, dev->cid.longname, snd_strerror(err)); goto OnErrorExit; } - AFB_ApiNotice(source->api, "AlsaCtlOpenByPath: sndcard hw:%d id=%s name=%s", cardIndex, cardId, cardName); + AFB_ApiNotice(mixer->api, "AlsaCtlOpenByPath: uid=%s cardid=%s cardname=%s", uid, dev->cid.cardid, dev->cid.longname); free(cardInfo); return handle; diff --git a/plugins/alsa/alsa-utils-dump.c b/plugins/alsa/alsa-utils-dump.c index 9ff07d8..3359a39 100644 --- a/plugins/alsa/alsa-utils-dump.c +++ b/plugins/alsa/alsa-utils-dump.c @@ -18,8 +18,22 @@ #define _GNU_SOURCE // needed for vasprintf - #include "alsa-softmixer.h" +#include + +PUBLIC json_object *AlsaDumpObjF(const char *format, ...) { + assert (format); + va_list args; + + char *result; + va_start(args, format); + int error= vasprintf(&result, format, args); + va_end(args); + + if (error < 0) return NULL; + + return (json_object_new_string(result)); +} PUBLIC char *AlsaDumpPcmUid(snd_pcm_t *pcmHandle, char *buffer, size_t len) { snd_pcm_info_t *pcmInfo; @@ -56,7 +70,7 @@ OnErrorExit: return NULL; } -PUBLIC void AlsaDumpFormats(CtlSourceT *source, snd_pcm_t *pcmHandle) { +PUBLIC void AlsaDumpFormats(SoftMixerT *mixer, snd_pcm_t *pcmHandle) { char string[32]; snd_pcm_format_t format; snd_pcm_hw_params_t *pxmHwParams; @@ -65,15 +79,15 @@ PUBLIC void AlsaDumpFormats(CtlSourceT *source, snd_pcm_t *pcmHandle) { snd_pcm_hw_params_alloca(&pxmHwParams); snd_pcm_hw_params_any(pcmHandle, pxmHwParams); - AFB_ApiNotice(source->api, "Available formats: PCM=%s", ALSA_PCM_UID(pcmHandle, string)); + AFB_ApiNotice(mixer->api, "Available formats: PCM=%s", ALSA_PCM_UID(pcmHandle, string)); for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) { if (snd_pcm_hw_params_test_format(pcmHandle, pxmHwParams, format) == 0) { - AFB_ApiNotice(source->api, "- %s", snd_pcm_format_name(format)); + AFB_ApiNotice(mixer->api, "- %s", snd_pcm_format_name(format)); } } } -PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) { +PUBLIC void AlsaDumpCtlSubdev(SoftMixerT *mixer, snd_ctl_t *handle) { snd_ctl_card_info_t *cardInfo; int err; int dev = -1; @@ -91,7 +105,7 @@ PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) { while (1) { if (snd_ctl_pcm_next_device(handle, &dev) < 0) { - AFB_ApiError(source->api, "AlsaDumpCard: fail to open subdev card id=%s name=%s", cardId, cardName); + AFB_ApiError(mixer->api, "AlsaDumpCard: fail to open subdev card id=%s name=%s", cardId, cardName); goto OnErrorExit; } @@ -101,11 +115,11 @@ PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) { // ignore empty device slot if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { if (err != -ENOENT) - AFB_ApiError(source->api, "control digital audio info (%s): %s", cardName, snd_strerror(err)); + AFB_ApiError(mixer->api, "control digital audio info (%s): %s", cardName, snd_strerror(err)); continue; } - AFB_ApiNotice(source->api, "AlsaDumpCard card %d: %s [%s], device %d: %s [%s]", + AFB_ApiNotice(mixer->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 @@ -115,12 +129,12 @@ PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) { for (unsigned int idx = 0; idx < subdevCount; idx++) { snd_pcm_info_set_subdevice(pcminfo, idx); if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { - AFB_ApiError(source->api, "AlsaDumpCard: control digital audio playback info %i: %s", cardIndex, snd_strerror(err)); + AFB_ApiError(mixer->api, "AlsaDumpCard: control digital audio playback info %i: %s", cardIndex, snd_strerror(err)); } else { - AFB_ApiNotice(source->api, "AlsaDumpCard: -- Subdevice #%d: %s", idx, snd_pcm_info_get_subdevice_name(pcminfo)); + AFB_ApiNotice(mixer->api, "AlsaDumpCard: -- Subdevice #%d: %s", idx, snd_pcm_info_get_subdevice_name(pcminfo)); } } - AFB_ApiNotice(source->api, "AlsaDumpCard => subdevice count=%d avaliable=%d", subdevCount, subdevAvail); + AFB_ApiNotice(mixer->api, "AlsaDumpCard => subdevice count=%d avaliable=%d", subdevCount, subdevAvail); } return; @@ -128,18 +142,18 @@ OnErrorExit: return; } -PUBLIC void AlsaDumpPcmParams(CtlSourceT *source, snd_pcm_hw_params_t *pcmHwParams) { +PUBLIC void AlsaDumpPcmParams(SoftMixerT *mixer, snd_pcm_hw_params_t *pcmHwParams) { snd_output_t *output; char *buffer; snd_output_buffer_open(&output); snd_pcm_hw_params_dump(pcmHwParams, output); snd_output_buffer_string(output, &buffer); - AFB_ApiNotice(source->api, "AlsaPCMDump: %s", buffer); + AFB_ApiNotice(mixer->api, "AlsaPCMDump: %s", buffer); snd_output_close(output); } -PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, const char* info, snd_pcm_t *pcm) { +PUBLIC void AlsaDumpPcmInfo(SoftMixerT *mixer, const char* info, snd_pcm_t *pcm) { snd_output_t *out; char *buffer; @@ -150,18 +164,18 @@ PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, const char* info, snd_pcm_t *pcm snd_pcm_dump(pcm, out); snd_output_buffer_string(out, &buffer); - AFB_ApiNotice(source->api, "AlsaPCMDump: %s", buffer); + AFB_ApiNotice(mixer->api, "AlsaPCMDump: %s", buffer); snd_output_close(out); } -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 AlsaDumpElemConfig(SoftMixerT *mixer, const char* info, const char* elem) { + snd_config_update(); + snd_config_t *pcmConfig; + snd_config_search(snd_config, elem, &pcmConfig); + AlsaDumpCtlConfig(mixer, info, pcmConfig, 1); } -PUBLIC void AlsaDumpCtlConfig(CtlSourceT *source, const char* info, snd_config_t *config, int indent) { +PUBLIC void AlsaDumpCtlConfig(SoftMixerT *mixer, const char* info, snd_config_t *config, int indent) { snd_config_iterator_t it, next; // hugly hack to get minimalist indentation @@ -184,28 +198,28 @@ PUBLIC void AlsaDumpCtlConfig(CtlSourceT *source, const char* info, snd_config_t 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); + AFB_ApiNotice(mixer->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); + AFB_ApiNotice(mixer->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); + AFB_ApiNotice(mixer->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); + AFB_ApiNotice(mixer->api, "%s: %s %s { ", info, pretty, key); + AlsaDumpCtlConfig(mixer, info, node, indent + 2); + AFB_ApiNotice(mixer->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); + AFB_ApiNotice(mixer->api, "%s: %s: key=%s unknown=%s", info, pretty, key, valueS); break; } } -- cgit 1.2.3-korg