From 6f13e28ba698a2b0145acbb926b79cd569a31f44 Mon Sep 17 00:00:00 2001 From: Fulup Ar Foll Date: Sun, 13 May 2018 22:07:22 +0200 Subject: First testable version. Mixing with volume and mute per audio role works. --- plugins/alsa/alsa-api-sndcards.c | 118 ++++++++----- plugins/alsa/alsa-api-sndloops.c | 80 ++++++--- plugins/alsa/alsa-api-sndstreams.c | 166 ++++++++++++------ plugins/alsa/alsa-api-sndzones.c | 3 +- plugins/alsa/alsa-core-ctl.c | 336 ++++++++++++++++++++++++++++--------- plugins/alsa/alsa-core-pcm.c | 29 ++-- plugins/alsa/alsa-plug-dmix.c | 4 +- plugins/alsa/alsa-plug-multi.c | 4 +- plugins/alsa/alsa-plug-rate.c | 81 +++++++++ plugins/alsa/alsa-plug-route.c | 4 +- plugins/alsa/alsa-plug-stream.c | 19 ++- plugins/alsa/alsa-softmixer.c | 82 --------- plugins/alsa/alsa-softmixer.h | 27 ++- plugins/alsa/alsa-utils-bypath.c | 1 - 14 files changed, 637 insertions(+), 317 deletions(-) create mode 100644 plugins/alsa/alsa-plug-rate.c (limited to 'plugins') diff --git a/plugins/alsa/alsa-api-sndcards.c b/plugins/alsa/alsa-api-sndcards.c index e98a294..daa996a 100644 --- a/plugins/alsa/alsa-api-sndcards.c +++ b/plugins/alsa/alsa-api-sndcards.c @@ -25,77 +25,121 @@ extern Lua2cWrapperT Lua2cWrap; STATIC int ProcessOneChannel(CtlSourceT *source, const char* uid, json_object *channelJ, AlsaPcmChannels *channel) { const char*channelUid; - + int error = wrap_json_unpack(channelJ, "{ss,si !}", "uid", &channelUid, "port", &channel->port); if (error) goto OnErrorExit; - channel->uid=strdup(channelUid); + 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)); + AFB_ApiError(source->api, "ProcessOneChannel: sndcard=%s channel: missing (uid||port) json=%s", uid, json_object_get_string(channelJ)); return -1; } -STATIC int ProcessSndParams(CtlSourceT *source, const char* uid, json_object *paramsJ, AlsaPcmHwInfoT *params) { - - int error = wrap_json_unpack(paramsJ, "{s?i,s?i !}", "rate", ¶ms->rate, "channels", ¶ms->channels); +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: - AFB_ApiError(source->api, "ProcessSndParams: sndcard=%s params: missing (rate|channel) params=%s", uid, json_object_get_string(paramsJ)); return -1; } STATIC int ProcessOneSndCard(CtlSourceT *source, json_object *sndcardJ, AlsaPcmInfoT *snd) { - json_object *sinkJ, *paramsJ=NULL; + 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); + 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; + + 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); + 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; + goto OnErrorExit; } else { - snd_pcm_close(dmixPcm->handle); - snd->cardid=dmixPcm->cardid; + snd->cardid = dmixPcm->cardid; } switch (json_object_get_type(sinkJ)) { case json_type_object: - snd->ccount=1; - snd->channels = calloc(snd->ccount+1, sizeof (AlsaPcmChannels)); + snd->ccount = 1; + snd->channels = calloc(snd->ccount + 1, sizeof (AlsaPcmChannels)); error = ProcessOneChannel(source, snd->uid, sndcardJ, &snd->channels[0]); if (error) goto OnErrorExit; break; case json_type_array: - snd->ccount = (int)json_object_array_length(sinkJ); - snd->channels = calloc(snd->ccount+1, sizeof (AlsaPcmChannels)); + snd->ccount = (int) json_object_array_length(sinkJ); + snd->channels = calloc(snd->ccount + 1, sizeof (AlsaPcmChannels)); for (int idx = 0; idx < snd->ccount; idx++) { json_object *channelJ = json_object_array_get_idx(sinkJ, idx); error = ProcessOneChannel(source, snd->uid, channelJ, &snd->channels[idx]); @@ -115,13 +159,13 @@ OnErrorExit: CTLP_LUA2C(snd_cards, source, argsJ, responseJ) { AlsaPcmInfoT *sndcards; - + int error; size_t count; switch (json_object_get_type(argsJ)) { case json_type_object: - count= 1; + count = 1; sndcards = calloc(count + 1, sizeof (AlsaPcmInfoT)); error = ProcessOneSndCard(source, argsJ, &sndcards[0]); if (error) goto OnErrorExit; @@ -141,23 +185,21 @@ CTLP_LUA2C(snd_cards, source, argsJ, responseJ) { } // register Sound card and multi when needed - Softmixer->sndcardCtl= sndcards; - + Softmixer->sndcardCtl = sndcards; + if (count == 1) { - + // only one sound card we multi would be useless Softmixer->multiPcm = &sndcards[0]; - + } else { AlsaPcmInfoT *pcmMulti; - + // instantiate an alsa multi plugin - pcmMulti = AlsaCreateMulti(source, "PcmMulti"); + pcmMulti = AlsaCreateMulti(source, "PcmMulti", 0); if (!pcmMulti) goto OnErrorExit; - // Close Multi and save into globak handle for further use - snd_pcm_close(pcmMulti->handle); - Softmixer->multiPcm= pcmMulti; + Softmixer->multiPcm = pcmMulti; } return 0; diff --git a/plugins/alsa/alsa-api-sndloops.c b/plugins/alsa/alsa-api-sndloops.c index 5599ae0..be12c11 100644 --- a/plugins/alsa/alsa-api-sndloops.c +++ b/plugins/alsa/alsa-api-sndloops.c @@ -24,23 +24,34 @@ // Fulup need to be cleanup with new controller version extern Lua2cWrapperT Lua2cWrap; -STATIC int ProcessOneSubdev(CtlSourceT *source, AlsaSndLoopT *loop, json_object *subdevJ, AlsaPcmInfoT *subdev) { - - int error = wrap_json_unpack(subdevJ, "{si,si !}", "subdev", &subdev->subdev, "numid", &subdev->numid); +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); + 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->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->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) { @@ -55,27 +66,43 @@ OnErrorExit: } STATIC int ProcessOneLoop(CtlSourceT *source, json_object *loopJ, AlsaSndLoopT *loop) { - json_object *subdevsJ=NULL, *devicesJ=NULL; + json_object *subdevsJ = NULL, *devicesJ = NULL, *paramsJ = NULL; int error; - error = wrap_json_unpack(loopJ, "{ss,s?s,s?s,s?i,s?o,so}", "uid",&loop->uid, "devpath",&loop->devpath, "cardid",&loop->cardid - , "cardidx",&loop->cardidx, "devices",&devicesJ, "subdevs",&subdevsJ); + error = wrap_json_unpack(loopJ, "{ss,s?s,s?s,s?i,s?o,so,s?o !}", "uid", &loop->uid, "devpath", &loop->devpath, "cardid", &loop->cardid + , "cardidx", &loop->cardidx, "devices", &devicesJ, "subdevs", &subdevsJ, "params", ¶msJ); if (error || !loop->uid || !subdevsJ || (!loop->devpath && !loop->cardid && loop->cardidx)) { AFB_ApiNotice(source->api, "ProcessOneLoop missing 'uid|devpath|cardid|cardidx|devices|subdevs' loop=%s", json_object_get_string(loopJ)); goto OnErrorExit; } - + + AlsaPcmHwInfoT *loopDefParams =alloca(sizeof(AlsaPcmHwInfoT)); + if (paramsJ) { + error = ProcessSndParams(source, loop->uid, paramsJ, loopDefParams); + if (error) { + AFB_ApiError(source->api, "ProcessOneLoop: sndcard=%s invalid params=%s", loop->uid, json_object_get_string(paramsJ)); + goto OnErrorExit; + } + } else { + loopDefParams->rate = ALSA_DEFAULT_PCM_RATE; + loopDefParams->rate= ALSA_DEFAULT_PCM_RATE; + loopDefParams->access=SND_PCM_ACCESS_RW_INTERLEAVED; + loopDefParams->format=SND_PCM_FORMAT_S16_LE; + loopDefParams->channels=2; + loopDefParams->sampleSize=0; + } + // make sure useful information will not be removed - loop->uid=strdup(loop->uid); - if (loop->cardid) loop->cardid=strdup(loop->cardid); - if (loop->devpath) loop->cardid=strdup(loop->devpath); - + loop->uid = strdup(loop->uid); + if (loop->cardid) loop->cardid = strdup(loop->cardid); + if (loop->devpath) loop->cardid = strdup(loop->devpath); + // Default devices is payback=0 capture=1 if (!devicesJ) { - loop->playback=0; - loop->capture=1; + loop->playback = 0; + loop->capture = 1; } else { - error = wrap_json_unpack(devicesJ, "{si,si}", "capture",&loop->capture, "playback", &loop->playback); + 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; @@ -85,24 +112,24 @@ STATIC int ProcessOneLoop(CtlSourceT *source, json_object *loopJ, AlsaSndLoopT * switch (json_object_get_type(subdevsJ)) { case json_type_object: loop->scount = 1; - loop->subdevs = calloc(loop->scount+1, sizeof (AlsaPcmInfoT)); - error = ProcessOneSubdev(source, loop, subdevsJ, &loop->subdevs[0]); + 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)); + loop->scount = (int) json_object_array_length(subdevsJ); + loop->subdevs = calloc(loop->scount + 1, sizeof (AlsaPcmInfoT)); for (int idx = 0; idx < loop->scount; idx++) { json_object *subdevJ = json_object_array_get_idx(subdevsJ, idx); - error = ProcessOneSubdev(source, loop, subdevJ, &loop->subdevs[idx]); + error = ProcessOneSubdev(source, loop, subdevJ, loopDefParams, &loop->subdevs[idx]); if (error) goto OnErrorExit; } break; default: - AFB_ApiError(source->api, "L2C:ProcessOneLoop=%s invalid subdevs= %s", loop->uid, json_object_get_string(subdevsJ)); + AFB_ApiError(source->api, "L2C:ProcessOneLoop=%s invalid subdevs= %s", loop->uid, json_object_get_string(subdevsJ)); goto OnErrorExit; } - + return 0; OnErrorExit: @@ -111,7 +138,7 @@ OnErrorExit: CTLP_LUA2C(snd_loops, source, argsJ, responseJ) { int error; - AlsaSndLoopT *sndLoop = calloc (1, sizeof(AlsaSndLoopT)); + AlsaSndLoopT *sndLoop = calloc(1, sizeof (AlsaSndLoopT)); if (json_object_get_type(argsJ) != json_type_object) { AFB_ApiError(source->api, "L2C:sndloops: invalid object type= %s", json_object_get_string(argsJ)); @@ -127,7 +154,6 @@ CTLP_LUA2C(snd_loops, source, argsJ, responseJ) { // register routed into global softmixer handle Softmixer->loopCtl = sndLoop; - return 0; OnErrorExit: diff --git a/plugins/alsa/alsa-api-sndstreams.c b/plugins/alsa/alsa-api-sndstreams.c index 1ee0029..d02d47e 100644 --- a/plugins/alsa/alsa-api-sndstreams.c +++ b/plugins/alsa/alsa-api-sndstreams.c @@ -26,39 +26,32 @@ extern Lua2cWrapperT Lua2cWrap; STATIC int ProcessOneStream(CtlSourceT *source, json_object *streamJ, AlsaSndStreamT *stream) { int error; - const char*format=NULL; + json_object *paramsJ = NULL; - error = wrap_json_unpack(streamJ, "{ss,ss,s?i,s?b,s?i,s?i,s?i !}", "uid", &stream->uid, "zone", &stream->zone, "volume", &stream->volume, "mute", stream->mute - , "rate", &stream->params.rate, "format", &format, "access", &stream->params.access); + // Make sure default runs + stream->volume = ALSA_DEFAULT_PCM_VOLUME; + stream->mute = 0; + + error = wrap_json_unpack(streamJ, "{ss,ss,s?i,s?b,s?o !}", "uid", &stream->uid, "zone", &stream->zone, "volume", &stream->volume + , "mute", stream->mute, "params", ¶msJ); + if (error) { + AFB_ApiNotice(source->api, "ProcessOneStream missing 'uid|zone|volume|rate|mute|params' stream=%s", json_object_get_string(streamJ)); + goto OnErrorExit; + } + + if (paramsJ) error = ProcessSndParams(source, stream->uid, paramsJ, &stream->params); if (error) { - AFB_ApiNotice(source->api, "ProcessOneStream missing 'uid|zone|volume|rate|mute' stream=%s", json_object_get_string(streamJ)); + AFB_ApiError(source->api, "ProcessOneSndCard: sndcard=%s invalid params=%s", stream->uid, json_object_get_string(paramsJ)); goto OnErrorExit; + } else { + stream->params.rate = ALSA_DEFAULT_PCM_RATE; + stream->params.rate = ALSA_DEFAULT_PCM_RATE; + stream->params.access = SND_PCM_ACCESS_RW_INTERLEAVED; + stream->params.format = SND_PCM_FORMAT_S16_LE; + stream->params.channels = 2; + stream->params.sampleSize = 0; } - if (!format) stream->params.format=SND_PCM_FORMAT_UNKNOWN; - else if (!strcasecmp (format, "S16_LE")) stream->params.format=SND_PCM_FORMAT_S16_LE; - else if (!strcasecmp (format, "S16_BE")) stream->params.format=SND_PCM_FORMAT_S16_BE; - else if (!strcasecmp (format, "U16_LE")) stream->params.format=SND_PCM_FORMAT_U16_LE; - else if (!strcasecmp (format, "U16_BE")) stream->params.format=SND_PCM_FORMAT_U16_BE; - else if (!strcasecmp (format, "S32_LE")) stream->params.format=SND_PCM_FORMAT_S32_LE; - else if (!strcasecmp (format, "S32_BE")) stream->params.format=SND_PCM_FORMAT_S32_BE; - else if (!strcasecmp (format, "U32_LE")) stream->params.format=SND_PCM_FORMAT_U32_LE; - else if (!strcasecmp (format, "U32_BE")) stream->params.format=SND_PCM_FORMAT_U32_BE; - else if (!strcasecmp (format, "S24_LE")) stream->params.format=SND_PCM_FORMAT_S24_LE; - else if (!strcasecmp (format, "S24_BE")) stream->params.format=SND_PCM_FORMAT_S24_BE; - else if (!strcasecmp (format, "U24_LE")) stream->params.format=SND_PCM_FORMAT_U24_LE; - else if (!strcasecmp (format, "U24_BE")) stream->params.format=SND_PCM_FORMAT_U24_BE; - else if (!strcasecmp (format, "S8")) stream->params.format=SND_PCM_FORMAT_S8; - else if (!strcasecmp (format, "U8")) stream->params.format=SND_PCM_FORMAT_U8; - else if (!strcasecmp (format, "FLOAT_LE")) stream->params.format=SND_PCM_FORMAT_FLOAT_LE; - else if (!strcasecmp (format, "FLOAT_BE")) stream->params.format=SND_PCM_FORMAT_FLOAT_LE; - else { - AFB_ApiNotice(source->api, "ProcessOneStream unsupported format 'uid|zone|volume|rate|mute' stream=%s", json_object_get_string(streamJ)); - goto OnErrorExit; - } - - if(!stream->params.rate) stream->params.rate=ALSA_DEFAULT_PCM_RATE; - // make sure remain valid even when json object is removed stream->uid = strdup(stream->uid); stream->zone = strdup(stream->zone); @@ -72,6 +65,7 @@ OnErrorExit: CTLP_LUA2C(snd_streams, source, argsJ, responseJ) { AlsaSndStreamT *sndStream; int error; + long value; size_t count; // assert static/global softmixer handle get requited info @@ -111,13 +105,13 @@ CTLP_LUA2C(snd_streams, source, argsJ, responseJ) { // return stream data to application as a json array - *responseJ = json_object_new_array(); - + *responseJ = json_object_new_array(); + for (int idx = 0; sndStream[idx].uid != NULL; idx++) { - json_object *streamJ; - + json_object *streamJ, *paramsJ; + // Search for a free loop capture device - AFB_ApiNotice(source->api, "L2C:sndstreams stream=%s Start", (char*)sndStream[idx].uid); + AFB_ApiNotice(source->api, "L2C:sndstreams stream=%s Start", (char*) sndStream[idx].uid); ctlLoop->scount--; if (ctlLoop->scount < 0) { AFB_ApiError(source->api, "L2C:sndstreams no more subdev avaliable in loopback=%s", ctlLoop->uid); @@ -126,43 +120,107 @@ CTLP_LUA2C(snd_streams, source, argsJ, responseJ) { // 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); + playbackDev->device = ctlLoop->capture; + AlsaPcmInfoT *captureDev = AlsaByPathOpenPcm(source, playbackDev, SND_PCM_STREAM_CAPTURE); if (!captureDev) goto OnErrorExit; - - AlsaPcmInfoT *streamPcm = AlsaCreateStream(source, &sndStream[idx], captureDev); + + // configure with default loopback subdev params + error = AlsaPcmConf(source, captureDev, &playbackDev->params); + if (error) goto OnErrorExit; + + // Register capture PCM for active/pause event + if (captureDev->numid) { + error = AlsaCtlRegister(source, captureDev, captureDev->numid); + if (error) goto OnErrorExit; + } + + // Try to create/setup volume control. + snd_ctl_t* ctlDev = AlsaCrlFromPcm(source, captureDev->handle); + if (!ctlDev) { + AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail attache sndcard", captureDev->cardid); + goto OnErrorExit; + } + + // create mute control and register it as pause/resume ctl) + char runName[ALSA_CARDID_MAX_LEN]; + snprintf(runName, sizeof (runName), "run-%s", sndStream[idx].uid); + + // create a single boolean value control for pause/resume + int runNumid = AlsaCtlCreateControl(source, ctlDev, playbackDev, runName, 1, 0, 1, 1, !sndStream[idx].mute); + if (runNumid <= 0) goto OnErrorExit; + + // register mute/unmute as a pause/resume control + error = AlsaCtlRegister(source, captureDev, runNumid); + if (error) goto OnErrorExit; + + // create stream and delay pcm openning until vol control is created + char volName[ALSA_CARDID_MAX_LEN]; + snprintf(volName, sizeof (volName), "vol-%s", sndStream[idx].uid); + AlsaPcmInfoT *streamPcm = AlsaCreateStream(source, &sndStream[idx], captureDev, volName, 0); if (!streamPcm) { - AFB_ApiError(source->api, "L2C:sndstreams fail to create stream=%s", (char*) sndStream[idx].uid); + AFB_ApiError(source->api, "L2C:sndstreams:%s(pcm) fail to create stream", sndStream[idx].uid); + goto OnErrorExit; + } + + // create volume control before softvol pcm is opened + int volNumid = AlsaCtlCreateControl(source, ctlDev, playbackDev, volName, streamPcm->params.channels, 0, 100, 1, sndStream[idx].volume); + if (volNumid <= 0) goto OnErrorExit; + + // **** Fulup (would need some help to get automatic rate converter to work). + // // add a rate converter plugin to match stream params config + // char rateName[ALSA_CARDID_MAX_LEN]; + // snprintf(rateName, sizeof (rateName), "rate-%s", sndStream[idx].uid); + // AlsaPcmInfoT *ratePcm= AlsaCreateRate(source, rateName, streamPcm, 1); + // if (!ratePcm) { + // AFB_ApiError(source->api, "L2C:sndstreams:%s(pcm) fail to create rate converter", sndStream[idx].uid); + // goto OnErrorExit; + // } + + // everything is not ready to open capture pcm + error = snd_pcm_open(&streamPcm->handle, sndStream[idx].uid, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (error) { + AFB_ApiError(source->api, "L2C:sndstreams:%s(pcm) fail to open capture", sndStream[idx].uid); goto OnErrorExit; } - + // capture stream inherit channel from targeted zone captureDev->ccount = streamPcm->ccount; - sndStream[idx].params.channels=streamPcm->ccount; - - // start stream pcm copy - error = AlsaPcmCopy(source, captureDev, streamPcm, &sndStream[idx].params); + streamPcm->params.channels = streamPcm->ccount; + + // start stream pcm copy (at this both capture & sink pcm should be open) + error = AlsaPcmCopy(source, captureDev, streamPcm, &streamPcm->params); if (error) goto OnErrorExit; - // Registration to event should be done after pcm_start - if (captureDev->numid) { - error = AlsaCtlRegister(source, captureDev, captureDev->numid); - if (error) goto OnErrorExit; + // retrieve active/pause control and set PCM status accordingly + error = AlsaCtlNumidGetLong(source, ctlDev, captureDev->numid, &value); + if (error) goto OnErrorExit; + + // toggle pause/resume (should be done after pcm_start) + if ((error = snd_pcm_pause(captureDev->handle, !value)) < 0) { + AFB_ApiError(source->api, "L2C:sndstreams [capture=%s] fail to pause error=%s", captureDev->cardid, snd_strerror(error)); + goto OnErrorExit; } - + // prepare response for application - playbackDev->device= ctlLoop->playback; + playbackDev->device = ctlLoop->playback; error = AlsaByPathDevid(source, playbackDev); - wrap_json_pack(&streamJ, "{ss ss si}", "uid", sndStream[idx].uid, "alsa", playbackDev->cardid, "numid", captureDev->numid); - json_object_array_add(*responseJ,streamJ); - + + error += wrap_json_pack(¶msJ, "{si si si si}", "rate", streamPcm->params.rate, "channels", streamPcm->params.channels, "format", streamPcm->params.format, "access", streamPcm->params.access); + error += wrap_json_pack(&streamJ, "{ss ss si si so}", "uid", streamPcm->uid, "alsa", playbackDev->cardid, "volid", volNumid, "runid", runNumid, "params", paramsJ); + error += json_object_array_add(*responseJ, streamJ); + if (error) { + AFB_ApiError(source->api, "L2C:sndstreams:%s(stream) fail to prepare response", captureDev->cardid); + goto OnErrorExit; + } + + snd_ctl_close(ctlDev); // Debug Alsa Config //AlsaDumpElemConfig (source, "\n\nAlsa_Config\n------------\n", "pcm"); //AlsaDumpPcmInfo(source, "\n\nPcm_config\n-----------\n", streamPcm->handle); - AFB_ApiNotice(source->api, "L2C:sndstreams stream=%s OK\n", (char*) sndStream[idx].uid); + AFB_ApiNotice(source->api, "L2C:sndstreams:%s(stream) OK reponse=%s\n", streamPcm->uid, json_object_get_string(streamJ)); } return 0; diff --git a/plugins/alsa/alsa-api-sndzones.c b/plugins/alsa/alsa-api-sndzones.c index 11d77c2..7ca61ad 100644 --- a/plugins/alsa/alsa-api-sndzones.c +++ b/plugins/alsa/alsa-api-sndzones.c @@ -128,12 +128,11 @@ CTLP_LUA2C(snd_zones, source, argsJ, responseJ) { // instantiate one route PCM per zone with multi plugin as slave for (int idx = 0; sndZone[idx].uid != NULL; idx++) { - Softmixer->zonePcms[idx] = AlsaCreateRoute(source, &sndZone[idx]); + Softmixer->zonePcms[idx] = AlsaCreateRoute(source, &sndZone[idx], 0); if (!Softmixer->zonePcms[idx]) { AFB_ApiNotice(source->api, "L2C:sndzones fail to create route zone=%s", sndZone[idx].uid); goto OnErrorExit; } - snd_pcm_close(Softmixer->zonePcms[idx]->handle); } // do not need this handle anymore diff --git a/plugins/alsa/alsa-core-ctl.c b/plugins/alsa/alsa-core-ctl.c index 38ce8cc..79bf0c8 100644 --- a/plugins/alsa/alsa-core-ctl.c +++ b/plugins/alsa/alsa-core-ctl.c @@ -46,11 +46,13 @@ typedef struct { typedef struct { SubStreamT stream[MAX_AUDIO_STREAMS + 1]; int count; + snd_ctl_t *ctlDev; } AudioStreamHandleT; static AudioStreamHandleT AudioStreamHandle; -STATIC snd_ctl_elem_id_t *AlsaCtlGetElemId(CtlSourceT *source, snd_ctl_t* ctlDev, int numid) { + +PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNumidElemId(CtlSourceT *source, snd_ctl_t* ctlDev, int numid) { char string[32]; int error; int index; @@ -60,18 +62,18 @@ STATIC snd_ctl_elem_id_t *AlsaCtlGetElemId(CtlSourceT *source, snd_ctl_t* ctlDev snd_ctl_elem_list_alloca(&ctlList); if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { - AFB_ApiError(source->api, "AlsaCtlElemIdGetInt [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string)); + AFB_ApiError(source->api, "AlsaCtlGetNumidElemId [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string)); goto OnErrorExit; } if ((error = snd_ctl_elem_list_alloc_space(ctlList, snd_ctl_elem_list_get_count(ctlList))) < 0) { - AFB_ApiError(source->api, "AlsaCtlElemIdGetInt [%s] fail retrieve count", ALSA_CTL_UID(ctlDev, string)); + AFB_ApiError(source->api, "AlsaCtlGetNumidElemId [%s] fail retrieve count", ALSA_CTL_UID(ctlDev, string)); goto OnErrorExit; } // Fulup: do not understand why snd_ctl_elem_list should be call twice to get a valid ctlCount if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { - AFB_ApiError(source->api, "AlsaCtlElemIdGetInt [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string)); + AFB_ApiError(source->api, "AlsaCtlGetNumidElemId [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string)); goto OnErrorExit; } @@ -87,7 +89,7 @@ STATIC snd_ctl_elem_id_t *AlsaCtlGetElemId(CtlSourceT *source, snd_ctl_t* ctlDev } if (index == ctlCount) { - AFB_ApiError(source->api, "AlsaCtlRegister [%s] fail get numid=%i count", ALSA_CTL_UID(ctlDev, string), numid); + AFB_ApiError(source->api, "AlsaCtlGetNumidElemId [%s] fail get numid=%i count", ALSA_CTL_UID(ctlDev, string), numid); goto OnErrorExit; } @@ -100,6 +102,55 @@ OnErrorExit: return NULL; } +PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(CtlSourceT *source, snd_ctl_t* ctlDev, const char *ctlName) { + char string[32]; + int error; + int index; + snd_ctl_elem_list_t *ctlList = NULL; + snd_ctl_elem_id_t *elemId; + + snd_ctl_elem_list_alloca(&ctlList); + + if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { + AFB_ApiError(source->api, "AlsaCtlGetNameElemId [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string)); + goto OnErrorExit; + } + + if ((error = snd_ctl_elem_list_alloc_space(ctlList, snd_ctl_elem_list_get_count(ctlList))) < 0) { + AFB_ApiError(source->api, "AlsaCtlGetNameElemId [%s] fail retrieve count", ALSA_CTL_UID(ctlDev, string)); + goto OnErrorExit; + } + + // Fulup: do not understand why snd_ctl_elem_list should be call twice to get a valid ctlCount + if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { + AFB_ApiError(source->api, "AlsaCtlGetNameElemId [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string)); + goto OnErrorExit; + } + + // loop on control to find the right one + int ctlCount = snd_ctl_elem_list_get_used(ctlList); + for (index = 0; index < ctlCount; index++) { + + if (!strcasecmp(ctlName, snd_ctl_elem_list_get_name(ctlList, index))) { + snd_ctl_elem_id_malloc(&elemId); + snd_ctl_elem_list_get_id(ctlList, index, elemId); + break; + } + } + + if (index == ctlCount) { + AFB_ApiError(source->api, "AlsaCtlGetNameElemId [%s] fail get ctl name=%s", ALSA_CTL_UID(ctlDev, string), ctlName); + goto OnErrorExit; + } + + // clear ctl list and return elemid + snd_ctl_elem_list_clear(ctlList); + return elemId; + +OnErrorExit: + if (ctlList) snd_ctl_elem_list_clear(ctlList); + return NULL; +} PUBLIC snd_ctl_t *AlsaCtlOpenCtl(CtlSourceT *source, const char *cardid) { int error; @@ -135,7 +186,50 @@ OnErrorExit: return -1; } -STATIC int CtlElemIdGetInt(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, long *value) { +STATIC void CtlElemIdDisplay(AFB_ApiT api, 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); + const char* name = snd_ctl_elem_info_get_name(elemInfo); + snd_ctl_elem_type_t elemType = snd_ctl_elem_info_get_type(elemInfo); + + + if (!elemData) { + AFB_ApiWarning(api, "CtlElemIdDisplay: numid=%d name=%s value=unreadable", numid, name); + } else + for (int idx = 0; idx < count; idx++) { + long valueL; + + switch (elemType) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + valueL = snd_ctl_elem_value_get_boolean(elemData, idx); + AFB_ApiWarning(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); + 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); + 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); + 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); + break; + case SND_CTL_ELEM_TYPE_IEC958: + default: + AFB_ApiWarning(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) { int error; snd_ctl_elem_value_t *elemData; snd_ctl_elem_info_t *elemInfo; @@ -146,58 +240,68 @@ STATIC int CtlElemIdGetInt(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *e if (!snd_ctl_elem_info_is_readable(elemInfo)) goto OnErrorExit; // as we have static rate/channel we should have only one boolean as value - snd_ctl_elem_type_t elemType = snd_ctl_elem_info_get_type(elemInfo); - int count = snd_ctl_elem_info_get_count(elemInfo); - if (count != 1) goto OnErrorExit; snd_ctl_elem_value_alloca(&elemData); snd_ctl_elem_value_set_id(elemData, elemId); error = snd_ctl_elem_read(ctlDev, elemData); - if (error) goto OnSuccessExit; + 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 = snd_ctl_elem_value_get_integer(elemData, 0); + *value = (int) snd_ctl_elem_value_get_integer(elemData, 0); -OnSuccessExit: return 0; OnErrorExit: + CtlElemIdDisplay(api, elemInfo, elemData); + return -1; +} - AFB_ApiWarning(api, "CtlSubscribeEventCB: ignored unsupported event Numid=%i", snd_ctl_elem_info_get_numid(elemInfo)); - for (int idx = 0; idx < count; idx++) { - long valueL; - - switch (elemType) { - case SND_CTL_ELEM_TYPE_BOOLEAN: - valueL = snd_ctl_elem_value_get_boolean(elemData, idx); - AFB_ApiNotice(api, "CtlElemIdGetBool: value=%ld", valueL); - break; - case SND_CTL_ELEM_TYPE_INTEGER: - valueL = snd_ctl_elem_value_get_integer(elemData, idx); - AFB_ApiNotice(api, "CtlElemIdGetInt: value=%ld", valueL); - break; - case SND_CTL_ELEM_TYPE_INTEGER64: - valueL = snd_ctl_elem_value_get_integer64(elemData, idx); - AFB_ApiNotice(api, "CtlElemIdGetInt64: value=%ld", valueL); - break; - case SND_CTL_ELEM_TYPE_ENUMERATED: - valueL = snd_ctl_elem_value_get_enumerated(elemData, idx); - AFB_ApiNotice(api, "CtlElemIdGetEnum: value=%ld", valueL); - break; - case SND_CTL_ELEM_TYPE_BYTES: - valueL = snd_ctl_elem_value_get_byte(elemData, idx); - AFB_ApiNotice(api, "CtlElemIdGetByte: value=%ld", valueL); - break; - case SND_CTL_ELEM_TYPE_IEC958: - default: - AFB_ApiNotice(api, "CtlElemIdGetInt: Unsupported type=%d", elemType); - break; - } +PUBLIC int CtlElemIdSetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, long value) { + snd_ctl_elem_value_t *elemData; + snd_ctl_elem_info_t *elemInfo; + const char* name; + int error, numid; + + 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_writable(elemInfo)) goto OnErrorExit; + + int count = snd_ctl_elem_info_get_count(elemInfo); + if (count == 0) goto OnErrorExit; + + snd_ctl_elem_value_alloca(&elemData); + snd_ctl_elem_value_set_id(elemData, elemId); + error = snd_ctl_elem_read(ctlDev, elemData); + if (error) goto OnErrorExit; + + + for (int index = 0; index < count; index++) { + snd_ctl_elem_value_set_integer(elemData, index, value); } + + error = snd_ctl_elem_write(ctlDev, elemData); + if (error) goto OnErrorExit; + + return 0; + +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); return -1; } // Clone of AlsaLib snd_card_load2 static function + PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(CtlSourceT *source, const char *cardid) { int error; snd_ctl_t *ctlDev; @@ -220,15 +324,34 @@ OnErrorExit: return NULL; } -PUBLIC int AlsaCtlGetNumidValueI(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long* value) { - - snd_ctl_elem_id_t *elemId = AlsaCtlGetElemId(source, ctlDev, numid); +PUBLIC int AlsaCtlNumidSetLong(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long value) { + + snd_ctl_elem_id_t *elemId = AlsaCtlGetNumidElemId(source, ctlDev, numid); + if (!elemId) { + AFB_ApiError(source->api, "AlsaCtlNumidSetLong [sndcard=%s] fail to find numid=%d", snd_ctl_name(ctlDev), numid); + goto OnErrorExit; + } + + int error = CtlElemIdSetLong(source->api, ctlDev, elemId, value); + if (error) { + AFB_ApiError(source->api, "AlsaCtlNumidSetLong [sndcard=%s] fail to set numid=%d value=%ld", snd_ctl_name(ctlDev), numid, value); + goto OnErrorExit; + } + + return 0; +OnErrorExit: + return -1; +} + +PUBLIC int AlsaCtlNumidGetLong(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long* value) { + + snd_ctl_elem_id_t *elemId = AlsaCtlGetNumidElemId(source, ctlDev, numid); if (!elemId) { AFB_ApiError(source->api, "AlsaCtlGetNumValueI [sndcard=%s] fail to find numid=%d", snd_ctl_name(ctlDev), numid); goto OnErrorExit; } - int error = CtlElemIdGetInt(source->api, ctlDev, elemId, value); + int error = CtlElemIdGetLong(source->api, ctlDev, elemId, value); if (error) { AFB_ApiError(source->api, "AlsaCtlGetNumValueI [sndcard=%s] fail to get numid=%d value", snd_ctl_name(ctlDev), numid); goto OnErrorExit; @@ -239,6 +362,79 @@ 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) { + snd_ctl_elem_type_t ctlType; + snd_ctl_elem_info_t *elemInfo; + int error; + + 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); + + // softvol plugin is bugged and can only 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, 0); + snd_ctl_elem_info_set_subdevice(elemInfo, 0); + + // only two types implemented + if (ctlMin == 0 && ctlMax == 1) ctlType = SND_CTL_ELEM_TYPE_BOOLEAN; + else ctlType = SND_CTL_ELEM_TYPE_INTEGER; + + switch (ctlType) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + error = snd_ctl_add_boolean_elem_set(ctlDev, 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); + if (error) goto OnErrorExit; + break; + + default: + AFB_ApiError(source->api, "AlsaCtlMakeControl:%s(subdev) fail to create %s(control)", subdev->uid, ctlName); + goto OnErrorExit; + } + + // retrieve newly created control numid + int numid = snd_ctl_elem_info_get_numid(elemInfo); + return numid; + +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) { + int numid = -1; + + // if control does not exist then create + snd_ctl_elem_id_t *elemId = AlsaCtlGetNameElemId(source, ctlDev, 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); + if (numid <= 0) { + AFB_ApiError(source->api, "AlsaCtlCreateControl [sndcard=%s] fail to create ctlName=%s", snd_ctl_name(ctlDev), ctlName); + goto OnErrorExit; + } + + elemId = AlsaCtlGetNumidElemId(source, ctlDev, numid); + } + + int error = CtlElemIdSetLong(source->api, ctlDev, elemId, value); + if (error) { + AFB_ApiError(source->api, "AlsaCtlCreateControl [sndcard=%s] fail to set ctlName=%s Numid=%d", snd_ctl_name(ctlDev), ctlName, numid); + goto OnErrorExit; + } + + 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; @@ -270,7 +466,7 @@ 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 = CtlElemIdGetInt(subscribeHandle->api, subscribeHandle->ctlDev, elemId, &value); + error = CtlElemIdGetLong(subscribeHandle->api, subscribeHandle->ctlDev, elemId, &value); if (error) goto OnErrorExit; error = CtlElemIdGetNumid(subscribeHandle->api, subscribeHandle->ctlDev, elemId, &numid); @@ -286,15 +482,15 @@ STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, v } if (idx == AudioStreamHandle.count) { char cardName[32]; - ALSA_CTL_UID(subscribeHandle->ctlDev,cardName); - AFB_ApiWarning(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d card=%s numid=%d (ignored)", subscribeHandle->info, subscribeHandle->tid, cardName, numid); + ALSA_CTL_UID(subscribeHandle->ctlDev, cardName); + AFB_ApiWarning(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d card=%s numid=%d (ignored)", subscribeHandle->info, subscribeHandle->tid, cardName, numid); } - + OnSuccessExit: return 0; OnErrorExit: - AFB_ApiWarning(subscribeHandle->api, "CtlSubscribeEventCB: ignored unsupported event"); + AFB_ApiInfo(subscribeHandle->api, "CtlSubscribeEventCB: ignored unsupported event"); return 0; } @@ -340,7 +536,6 @@ OnErrorExit: return NULL; } - PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t * ctlDev) { int error; char string [32]; @@ -389,43 +584,32 @@ OnErrorExit: } PUBLIC int AlsaCtlRegister(CtlSourceT *source, AlsaPcmInfoT *pcm, int numid) { - long value; - int error; - // NumID are attached to sndcard retrieve ctldev from PCM - snd_ctl_t* ctlDev = AlsaCrlFromPcm(source, pcm->handle); - if (!ctlDev) { - AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail attache sndcard", pcm->cardid); + + int count = AudioStreamHandle.count; + if (count > MAX_AUDIO_STREAMS) { + AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] to many audio stream max=%d", pcm->cardid, MAX_AUDIO_STREAMS); goto OnErrorExit; } - // This is the first registration let's subscrive to event - if (AudioStreamHandle.count == 0) { + // If 1st registration then open a dev control channel to recieve events + if (!AudioStreamHandle.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); } - error = AlsaCtlGetNumidValueI(source, ctlDev, numid, &value); - if (error) goto OnErrorExit; - - AFB_ApiNotice(source->api, "AlsaCtlRegister [pcm=%s] numid=%d value=%ld", pcm->cardid, numid, value); - // store PCM in order to pause/resume depending on event - int count=AudioStreamHandle.count; AudioStreamHandle.stream[count].pcm = pcm; AudioStreamHandle.stream[count].numid = numid; - - // we only need to keep ctldev open for initial registration - if (AudioStreamHandle.count++ > 0) snd_ctl_close(ctlDev); - - // toggle pause/resume (should be done after pcm_start) - if ((error = snd_pcm_pause(pcm->handle, !value)) < 0) { - AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail to pause", pcm->cardid); - goto OnErrorExit; - } + AudioStreamHandle.count++; return 0; OnErrorExit: - return -1; } diff --git a/plugins/alsa/alsa-core-pcm.c b/plugins/alsa/alsa-core-pcm.c index f143eb6..bc676ef 100644 --- a/plugins/alsa/alsa-core-pcm.c +++ b/plugins/alsa/alsa-core-pcm.c @@ -29,9 +29,6 @@ for the specific language governing permissions and #include - -#define BUFFER_FRAME_COUNT 1024 - typedef struct { snd_pcm_t *pcmIn; snd_pcm_t *pcmOut; @@ -238,7 +235,7 @@ STATIC int AlsaPcmReadCB(sd_event_source* src, int fd, uint32_t revents, void* u // In/Out frames transfer through buffer copy framesOut = snd_pcm_writei(pcmCopyHandle->pcmOut, pcmCopyHandle->buffer, framesIn); if (framesOut < 0 || framesOut != framesIn) { - AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PcmOut=%s UNDERUN/SUSPEND frameOut=%ld", ALSA_PCM_UID(pcmCopyHandle->pcmOut, string), framesOut); + AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PcmOut=%s UNDERUN frame=%ld", ALSA_PCM_UID(pcmCopyHandle->pcmOut, string), (framesIn - framesOut)); goto ExitOnSuccess; } @@ -283,28 +280,26 @@ PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pc struct pollfd *pcmInFds; int error; - // prepare PCM for capture and replay - error = AlsaPcmConf(source, pcmIn, opts); + // input and output should match + error = AlsaPcmConf(source, pcmOut, 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)); + 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)); goto OnErrorExit; }; - - error = AlsaPcmConf(source, pcmOut, opts); + // prepare PCM for capture and replay + error = AlsaPcmConf(source, pcmIn, opts); if (error) goto OnErrorExit; // Prepare PCM for usage - if ((error = snd_pcm_prepare(pcmOut->handle)) < 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail to start PCM=%s error=%s", ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); + 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)); goto OnErrorExit; }; - - AlsaPcmCopyHandleT *pcmCopyHandle = malloc(sizeof (AlsaPcmCopyHandleT)); pcmCopyHandle->info = "pcmCpy"; pcmCopyHandle->pcmIn = pcmIn->handle; @@ -312,7 +307,7 @@ PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pc pcmCopyHandle->api = source->api; pcmCopyHandle->channels = opts->channels; pcmCopyHandle->frameSize = opts->channels * opts->sampleSize; - pcmCopyHandle->frameCount = BUFFER_FRAME_COUNT; + pcmCopyHandle->frameCount = ALSA_BUFFER_FRAMES_COUNT; pcmCopyHandle->buffer = malloc(pcmCopyHandle->frameCount * pcmCopyHandle->frameSize); // get FD poll descriptor for capture PCM @@ -350,8 +345,8 @@ PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pc return 0; OnErrorExit: - AFB_ApiError(source->api, "AlsaPcmCopy: Fail \n - pcmIn=%s \n - pcmOut=%s", ALSA_PCM_UID(pcmIn->handle, string), ALSA_PCM_UID(pcmOut->handle, string)); - + 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)); return -1; } diff --git a/plugins/alsa/alsa-plug-dmix.c b/plugins/alsa/alsa-plug-dmix.c index 22b5117..b06d5f6 100644 --- a/plugins/alsa/alsa-plug-dmix.c +++ b/plugins/alsa/alsa-plug-dmix.c @@ -25,7 +25,7 @@ static int uniqueIpcIndex = 1024; ALSA_PLUG_PROTO(dmix); -PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave) { +PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave, int open) { snd_config_t *dmixConfig, *slaveConfig, *elemConfig, *pcmConfig; AlsaPcmInfoT *pcmPlug= calloc(1,sizeof(AlsaPcmInfoT)); @@ -57,7 +57,7 @@ PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, Als error += snd_config_add(dmixConfig, slaveConfig); if (error) goto OnErrorExit; - error = _snd_pcm_dmix_open(&pcmPlug->handle, pcmPlug->cardid, snd_config, dmixConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); + if (open) error = _snd_pcm_dmix_open(&pcmPlug->handle, pcmPlug->cardid, snd_config, dmixConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); if (error) { AFB_ApiError(source->api, "AlsaCreateDmix: fail to create Dmix=%s Slave=%s Error=%s", pcmPlug->cardid, pcmSlave->cardid, snd_strerror(error)); goto OnErrorExit; diff --git a/plugins/alsa/alsa-plug-multi.c b/plugins/alsa/alsa-plug-multi.c index 6deca96..3e82e1e 100644 --- a/plugins/alsa/alsa-plug-multi.c +++ b/plugins/alsa/alsa-plug-multi.c @@ -22,7 +22,7 @@ ALSA_PLUG_PROTO(multi); -PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmUid) { +PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmUid, int open) { snd_config_t *multiConfig, *elemConfig, *slavesConfig, *slaveConfig, *bindingsConfig, *bindingConfig, *pcmConfig; int error = 0, channelIdx=0; @@ -79,7 +79,7 @@ PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmUid) { // update top config to access previous plugin PCM snd_config_update(); - error = _snd_pcm_multi_open(&pcmPlug->handle, pcmUid, snd_config, multiConfig, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (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; diff --git a/plugins/alsa/alsa-plug-rate.c b/plugins/alsa/alsa-plug-rate.c new file mode 100644 index 0000000..a9b9e23 --- /dev/null +++ b/plugins/alsa/alsa-plug-rate.c @@ -0,0 +1,81 @@ +/* + * 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(rate); + + +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; + + 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_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_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_add(pcmConfig, rateConfig); + if (error) { + AFB_ApiError(source->api, "AlsaCreateRate: fail to add configRATE=%s", pcmPlug->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 (error) { + AFB_ApiError(source->api, "AlsaCreateRate: fail to create Rate=%s Slave=%s Error=%s", pcmPlug->cardid, pcmSlave->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); + return pcmPlug; + +OnErrorExit: + AlsaDumpCtlConfig(source, "plug-rate", rateConfig, 1); + AFB_ApiNotice(source->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 2c8f3cb..8fa5de8 100644 --- a/plugins/alsa/alsa-plug-route.c +++ b/plugins/alsa/alsa-plug-route.c @@ -47,7 +47,7 @@ OnErrorExit: return -1; } -PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone) { +PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone, int open) { snd_config_t *routeConfig, *elemConfig, *slaveConfig, *tableConfig, *pcmConfig; int error = 0; @@ -115,7 +115,7 @@ PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone) { // update top config to access previous plugin PCM snd_config_update(); - error = _snd_pcm_route_open(&pcmPlug->handle, zone->uid, snd_config, routeConfig, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (open) error = _snd_pcm_route_open(&pcmPlug->handle, zone->uid, snd_config, routeConfig, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (error) { AFB_ApiError(source->api, "AlsaCreateRoute:zone(%s) fail to create Plug=%s error=%s", zone->uid, pcmPlug->cardid, snd_strerror(error)); goto OnErrorExit; diff --git a/plugins/alsa/alsa-plug-stream.c b/plugins/alsa/alsa-plug-stream.c index e0ef69a..ffb7010 100644 --- a/plugins/alsa/alsa-plug-stream.c +++ b/plugins/alsa/alsa-plug-stream.c @@ -36,11 +36,11 @@ STATIC AlsaPcmInfoT* SlaveZoneByUid(CtlSourceT *source, AlsaPcmInfoT **pcmZones return NULL; } -PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *ctlControl) { +PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *ctlControl, const char* ctlName, int open) { snd_config_t *streamConfig, *elemConfig, *slaveConfig, *controlConfig,*pcmConfig; int error = 0; - AlsaPcmInfoT *pcmPlug= malloc(sizeof(AlsaPcmInfoT)); + AlsaPcmInfoT *pcmPlug= calloc(1,sizeof(AlsaPcmInfoT)); // assert static/global softmixer handle get requited info AlsaSndLoopT *ctlLoop = Softmixer->loopCtl; @@ -57,16 +57,19 @@ PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream } // search for target zone uid - pcmPlug->uid= stream->uid; - pcmPlug->cardid= stream->uid; AlsaPcmInfoT *pcmSlave= SlaveZoneByUid (source, pcmZones, stream->zone); if (!pcmSlave || !pcmSlave->uid) { - AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) fail to find Zone=%s", pcmPlug->uid, stream->zone); + AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) fail to find Zone=%s", stream->uid, stream->zone); goto OnErrorExit; } // 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(); @@ -74,6 +77,8 @@ PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream error += snd_config_set_id (streamConfig, pcmPlug->cardid); error += snd_config_imake_string(&elemConfig, "type", "softvol"); error += snd_config_add(streamConfig, elemConfig); + error += snd_config_imake_integer(&elemConfig, "resolution", 99); // use 0-100% + error += snd_config_add(streamConfig, elemConfig); if (error) goto OnErrorExit; // add slave leaf @@ -85,7 +90,7 @@ PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream // add control leaf error += snd_config_make_compound(&controlConfig, "control", 0); - error += snd_config_imake_string(&elemConfig, "name", stream->uid); + 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_add(controlConfig, elemConfig); @@ -95,7 +100,7 @@ PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream // update top config to access previous plugin PCM snd_config_update(); - error = _snd_pcm_softvol_open(&pcmPlug->handle, stream->uid, snd_config, streamConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); + if (open) error = _snd_pcm_softvol_open(&pcmPlug->handle, stream->uid, snd_config, streamConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); if (error) { AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) fail to create Plug=%s Slave=%s error=%s", stream->uid, pcmPlug->cardid, pcmSlave->cardid, snd_strerror(error)); goto OnErrorExit; diff --git a/plugins/alsa/alsa-softmixer.c b/plugins/alsa/alsa-softmixer.c index bd9ee5e..23a01dd 100644 --- a/plugins/alsa/alsa-softmixer.c +++ b/plugins/alsa/alsa-softmixer.c @@ -33,85 +33,3 @@ CTLP_ONLOAD(plugin, callbacks) { return NULL; } -CTLP_LUA2C(AlsaDmix, source, argsJ, responseJ) { - json_object* subscribeArgsJ = NULL; - - int error = 0; - wrap_json_pack(&subscribeArgsJ, "{ss}", "value", "location"); - AFB_ApiNotice(source->api, "lua2c router with %s", json_object_to_json_string_ext(subscribeArgsJ, JSON_C_TO_STRING_PRETTY)); - - return error; -} - - - -CTLP_LUA2C(AlsaRouter, source, argsJ, responseJ) { - json_object *sndInJ, *sndOutJ, *paramsJ = NULL; - AlsaPcmInfoT *sndIn, *sndOut; - int error; - - // make sndIn/Out a pointer to get cleaner code - sndIn = calloc(1, sizeof (AlsaPcmInfoT)); - sndOut = calloc(1, sizeof (AlsaPcmInfoT)); - - // set pcm options to defaults - AlsaPcmHwInfoT *pcmOpts; - pcmOpts = calloc(1, sizeof (AlsaPcmHwInfoT)); - pcmOpts->format = SND_PCM_FORMAT_UNKNOWN; - pcmOpts->access = SND_PCM_ACCESS_RW_INTERLEAVED; - - error = wrap_json_unpack(argsJ, "{s:o,s:o,s?o}", "devin", &sndInJ, "devout", &sndOutJ, "params", ¶msJ); - if (error) { - AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter ARGS missing devIn|devOut args=%s", json_object_get_string(argsJ)); - goto OnErrorExit; - } - - error = wrap_json_unpack(sndInJ, "{s?s,s?s,s?i,s?i,s?i}", "path", &sndIn->devpath, "id", &sndIn->cardid, "numid", &sndIn->numid, "dev", &sndIn->device, "sub", &sndIn->subdev); - if (error || (!sndIn->devpath && !sndIn->cardid)) { - AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter DEV-IN missing 'path|id|dev|sub|numid' devin=%s", json_object_get_string(sndInJ)); - goto OnErrorExit; - } - - error = wrap_json_unpack(sndOutJ, "{s?s,s?s,s?i,s?i, s?i}", "path", &sndOut->devpath, "id", &sndOut->cardid, "numid", &sndOut->numid, "dev", &sndOut->device, "sub", &sndOut->subdev); - if (error || (!sndOut->devpath && !sndOut->cardid)) { - AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter DEV-OUT missing 'path|id|dev|sub' devout=%s", json_object_get_string(sndOutJ)); - goto OnErrorExit; - } - - if (paramsJ) if ((error = wrap_json_unpack(paramsJ, "{s?i, s?i, s?i, s?i}", "format", &pcmOpts->format, "access", &pcmOpts->access, "rate", &pcmOpts->rate, "channels", &pcmOpts->channels)) != 0) { - AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter PARAMS missing 'format|access|rate|channels' params=%s", json_object_get_string(paramsJ)); - goto OnErrorExit; - } - - AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter devin=%s devout=%s rate=%d channel=%d", sndIn->devpath, sndOut->devpath, pcmOpts->rate, pcmOpts->channels); - - // Check sndOut Exist and build a valid cardid config - error = AlsaByPathDevid(source, sndOut); - if (error) goto OnErrorExit; - - // open capture PCM - AlsaPcmInfoT *pcmIn = AlsaByPathOpenPcm(source, sndIn, SND_PCM_STREAM_CAPTURE); - if (!pcmIn) goto OnErrorExit; - - AlsaPcmInfoT *pcmDmix = AlsaCreateDmix(source, "DmixPlugPcm", sndOut); - if (!pcmDmix) goto OnErrorExit; - - //AlsaPcmInfoT *pcmVol = AlsaCreateVol(source, "SoftVol", sndIn, pcmDmix); - //if (!pcmVol) goto OnErrorExit; - - //error = AlsaPcmCopy(source, pcmIn, pcmVol, pcmOpts); - //if (error) goto OnErrorExit; - - // Registration to event should be done after pcm_start - if (sndIn->numid) { - error = AlsaCtlRegister(source, pcmIn, sndIn->numid); - if (error) goto OnErrorExit; - } - - return 0; - -OnErrorExit: - AFB_ApiNotice(source->api, "--lua2c-- ERROR AlsaRouter sndIn=%s sndOut=%s rate=%d channel=%d", sndIn->devpath, sndOut->devpath, pcmOpts->rate, pcmOpts->channels); - return -1; -} - diff --git a/plugins/alsa/alsa-softmixer.h b/plugins/alsa/alsa-softmixer.h index db499c3..3018402 100644 --- a/plugins/alsa/alsa-softmixer.h +++ b/plugins/alsa/alsa-softmixer.h @@ -33,9 +33,11 @@ #include #define MAINLOOP_WATCHDOG 30000 -#define MAX_AUDIO_STREAMS 8 +#define MAX_AUDIO_STREAMS 8*2 #define ALSA_DEFAULT_PCM_RATE 48000 -#define ALSA_CARDID_MAX_LEN 32 +#define ALSA_DEFAULT_PCM_VOLUME 80 +#define ALSA_BUFFER_FRAMES_COUNT 1024 +#define ALSA_CARDID_MAX_LEN 64 #define ALSA_PLUG_PROTO(plugin) \ @@ -110,11 +112,15 @@ typedef struct { extern SoftMixerHandleT *Softmixer; +// alsa-utils-bypath.c PUBLIC snd_ctl_card_info_t* AlsaByPathInfo(CtlSourceT *source, const char *control); PUBLIC AlsaPcmInfoT* AlsaByPathOpenPcm(CtlSourceT *source, AlsaPcmInfoT *dev, snd_pcm_stream_t direction); PUBLIC snd_ctl_t *AlsaByPathOpenCtl(CtlSourceT *source, AlsaPcmInfoT *dev); PUBLIC int AlsaByPathDevid(CtlSourceT *source, AlsaPcmInfoT *dev); +// alsa-api-*.c +PUBLIC int ProcessSndParams(CtlSourceT *source, const char* uid, json_object *paramsJ, AlsaPcmHwInfoT *params); + // alsa-utils-dump.c PUBLIC void AlsaDumpFormats(CtlSourceT *source, snd_pcm_t *pcmHandle); PUBLIC char *AlsaDumpPcmUid(snd_pcm_t *pcmHandle, char *buffer, size_t len); @@ -130,18 +136,25 @@ PUBLIC char *AlsaDumpCtlUid(snd_ctl_t *ctlHandle, char *buffer, size_t len); // 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); PUBLIC int AlsaCtlRegister(CtlSourceT *source, AlsaPcmInfoT *pcm, int numid); -PUBLIC int AlsaCtlGetNumidValueI(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long* value); +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 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); // alsa-core-pcm.c PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *opts); PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *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); -PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmName); -PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *pcmControl); -PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone); +PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave, int open); +PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmName, int open); +PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone, int open); +PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *ctlControl, const char* ctlName, int open); +PUBLIC AlsaPcmInfoT* AlsaCreateRate(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave, int open); #endif \ No newline at end of file diff --git a/plugins/alsa/alsa-utils-bypath.c b/plugins/alsa/alsa-utils-bypath.c index 7ceb66f..a857ab1 100644 --- a/plugins/alsa/alsa-utils-bypath.c +++ b/plugins/alsa/alsa-utils-bypath.c @@ -74,7 +74,6 @@ PUBLIC int AlsaByPathDevid(CtlSourceT *source, AlsaPcmInfoT *dev) { dev->cardid=malloc(ALSA_CARDID_MAX_LEN); snprintf((char*)dev->cardid, ALSA_CARDID_MAX_LEN, "hw:%i", dev->cardidx); cardInfo = AlsaCtlGetInfo(source, dev->cardid); - cardInfo = AlsaCtlGetInfo(source, dev->cardid); } if (!cardInfo) { -- cgit 1.2.3-korg