diff options
author | Fulup Ar Foll <fulup@iot.bzh> | 2018-05-18 13:31:36 +0200 |
---|---|---|
committer | Fulup Ar Foll <fulup@iot.bzh> | 2018-05-18 13:31:36 +0200 |
commit | 7454d66bb47349418f8f65b8d7bec79039a2be32 (patch) | |
tree | 9816b7c9d213c890adfcde58646e50e6545ebb70 /plugins | |
parent | 3fd11a5eb799a391351334b3580c5582a065f780 (diff) |
Implements volume ramping
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/alsa/alsa-api-backend.c | 10 | ||||
-rw-r--r-- | plugins/alsa/alsa-api-frontend.c | 121 | ||||
-rw-r--r-- | plugins/alsa/alsa-api-mixer.c | 8 | ||||
-rw-r--r-- | plugins/alsa/alsa-api-streams.c | 184 | ||||
-rw-r--r-- | plugins/alsa/alsa-api-zones.c | 10 | ||||
-rw-r--r-- | plugins/alsa/alsa-core-ctl.c | 61 | ||||
-rw-r--r-- | plugins/alsa/alsa-core-pcm.c | 32 | ||||
-rw-r--r-- | plugins/alsa/alsa-effect-ramp.c | 144 | ||||
-rw-r--r-- | plugins/alsa/alsa-plug-multi.c | 4 | ||||
-rw-r--r-- | plugins/alsa/alsa-plug-route.c | 4 | ||||
-rw-r--r-- | plugins/alsa/alsa-plug-vol.c | 10 | ||||
-rw-r--r-- | plugins/alsa/alsa-softmixer.c | 4 | ||||
-rw-r--r-- | plugins/alsa/alsa-softmixer.h | 73 |
13 files changed, 473 insertions, 192 deletions
diff --git a/plugins/alsa/alsa-api-backend.c b/plugins/alsa/alsa-api-backend.c index 948d30d..85d9566 100644 --- a/plugins/alsa/alsa-api-backend.c +++ b/plugins/alsa/alsa-api-backend.c @@ -23,7 +23,9 @@ // Fulup need to be cleanup with new controller version extern Lua2cWrapperT Lua2cWrap; -STATIC int ProcessOneChannel(CtlSourceT *source, const char* uid, json_object *channelJ, AlsaPcmChannels *channel) { + + +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); @@ -141,13 +143,13 @@ STATIC int ProcessOneSndCard(CtlSourceT *source, json_object *sndcardJ, AlsaPcmI switch (json_object_get_type(sinkJ)) { case json_type_object: snd->ccount = 1; - snd->channels = calloc(snd->ccount + 1, sizeof (AlsaPcmChannels)); + 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 (AlsaPcmChannels)); + 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]); @@ -204,7 +206,7 @@ PUBLIC int SndBackend(CtlSourceT *source, json_object *argsJ) { mixerHandle->multiPcm = &mixerHandle->backend[0]; } else { - + // instantiate an alsa multi plugin mixerHandle->multiPcm = AlsaCreateMulti(source, "PcmMulti", 0); if (!mixerHandle->multiPcm) goto OnErrorExit; diff --git a/plugins/alsa/alsa-api-frontend.c b/plugins/alsa/alsa-api-frontend.c index 91ce63f..d39c92e 100644 --- a/plugins/alsa/alsa-api-frontend.c +++ b/plugins/alsa/alsa-api-frontend.c @@ -24,9 +24,29 @@ // Fulup need to be cleanup with new controller version extern Lua2cWrapperT Lua2cWrap; -STATIC int ProcessOneSubdev(CtlSourceT *source, AlsaSndLoopT *loop, json_object *subdevJ, AlsaPcmHwInfoT *loopDefParams,AlsaPcmInfoT *subdev) { +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)); @@ -41,7 +61,7 @@ STATIC int ProcessOneSubdev(CtlSourceT *source, AlsaSndLoopT *loop, json_object } } else { // use global loop params definition as default - memcpy (&subdev->params, loopDefParams, sizeof(AlsaPcmHwInfoT)); + memcpy(&subdev->params, loopDefParams, sizeof (AlsaPcmHwInfoT)); } // create a fake uid and complete subdev info from loop handle char subuid[30]; @@ -66,17 +86,25 @@ OnErrorExit: } STATIC int ProcessOneLoop(CtlSourceT *source, json_object *loopJ, AlsaSndLoopT *loop) { - json_object *subdevsJ = NULL, *devicesJ = NULL, *paramsJ = NULL; + 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 !}", "uid", &loop->uid, "devpath", &loop->devpath, "cardid", &loop->cardid - , "cardidx", &loop->cardidx, "devices", &devicesJ, "subdevs", &subdevsJ, "params", ¶msJ); + 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)); + AlsaPcmHwInfoT *loopDefParams = alloca(sizeof (AlsaPcmHwInfoT)); if (paramsJ) { error = ProcessSndParams(source, loop->uid, paramsJ, loopDefParams); if (error) { @@ -85,11 +113,11 @@ STATIC int ProcessOneLoop(CtlSourceT *source, json_object *loopJ, AlsaSndLoopT * } } 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; + 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 @@ -104,11 +132,36 @@ STATIC int ProcessOneLoop(CtlSourceT *source, json_object *loopJ, AlsaSndLoopT * 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->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) { @@ -149,37 +202,37 @@ OnErrorExit: return -1; } -PUBLIC int SndFrontend (CtlSourceT *source, json_object *argsJ) { +PUBLIC int SndFrontend(CtlSourceT *source, json_object *argsJ) { SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; int error; - - assert (mixerHandle); - - if (mixerHandle->loop) { + + assert(mixerHandle); + + if (mixerHandle->frontend) { AFB_ApiError(source->api, "SndFrontend: mixer=%s SndFrontend already declared %s", mixerHandle->uid, json_object_get_string(argsJ)); - goto OnErrorExit; + goto OnErrorExit; } - - mixerHandle->loop= calloc(1, sizeof (AlsaSndLoopT)); - + + mixerHandle->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); + json_type type = json_object_get_type(argsJ); if (type == json_type_array) { - size_t count= json_object_array_length(argsJ); + size_t count = json_object_array_length(argsJ); if (count != 1) { AFB_ApiError(source->api, "SndFrontend: mixer=%s frontend only support on input driver args=%s", mixerHandle->uid, json_object_get_string(argsJ)); - goto OnErrorExit; + goto OnErrorExit; } - argsJ= json_object_array_get_idx(argsJ,0); - } - - type= json_object_get_type(argsJ); + argsJ = json_object_array_get_idx(argsJ, 0); + } + + type = json_object_get_type(argsJ); if (type != json_type_object) { AFB_ApiError(source->api, "SndFrontend: mixer=%s invalid object type= %s", mixerHandle->uid, json_object_get_string(argsJ)); goto OnErrorExit; } - error = ProcessOneLoop(source, argsJ, mixerHandle->loop); + error = ProcessOneLoop(source, argsJ, mixerHandle->frontend); if (error) { AFB_ApiError(source->api, "SndFrontend: mixer=%s invalid object= %s", mixerHandle->uid, json_object_get_string(argsJ)); goto OnErrorExit; diff --git a/plugins/alsa/alsa-api-mixer.c b/plugins/alsa/alsa-api-mixer.c index 31854b0..db5f129 100644 --- a/plugins/alsa/alsa-api-mixer.c +++ b/plugins/alsa/alsa-api-mixer.c @@ -79,7 +79,7 @@ static void MixerApiVerbCB(AFB_ReqT request) { if (streams) { streamsJ = json_object_new_array(); - AlsaSndStreamT *streams = mixerHandle->streams; + AlsaLoopStreamT *streams = mixerHandle->streams; for (int idx = 0; streams[idx].uid; idx++) { if (quiet) { json_object_array_add(streamsJ, json_object_new_string(streams[idx].uid)); @@ -126,7 +126,7 @@ static void MixerApiVerbCB(AFB_ReqT request) { } if (streamsJ) { - error = SndStreams(source, streamsJ, &responseJ); + error = LoopStreams(source, streamsJ, &responseJ); if (error) goto OnErrorExit; } @@ -168,7 +168,7 @@ CTLP_LUA2C(_mixer_new_, source, argsJ, responseJ) { // create mixer verb within API. error = afb_dynapi_add_verb(source->api, mixerHandle->uid, mixerHandle->info, MixerApiVerbCB, mixerHandle, NULL, 0); if (error) { - AFB_ApiError(source->api, "_mixer_new_ mixer=%s fail to register API verb", mixerHandle->uid); + AFB_ApiError(source->api, "_mixer_new_ mixer=%s fail to Registry API verb", mixerHandle->uid); return -1; } @@ -191,7 +191,7 @@ CTLP_LUA2C(_mixer_new_, source, argsJ, responseJ) { } if (streamsJ) { - error = SndStreams(source, streamsJ, responseJ); + error = LoopStreams(source, streamsJ, responseJ); if (error) goto OnErrorExit; } diff --git a/plugins/alsa/alsa-api-streams.c b/plugins/alsa/alsa-api-streams.c index d101a5a..56d3cf8 100644 --- a/plugins/alsa/alsa-api-streams.c +++ b/plugins/alsa/alsa-api-streams.c @@ -31,16 +31,30 @@ extern Lua2cWrapperT Lua2cWrap; typedef struct { const char* verb; - AlsaSndStreamT *streams; + AlsaLoopStreamT *stream; SoftMixerHandleT *mixer; + AlsaVolRampT *ramp; } apiHandleT; -static void StreamApiVerbCB(AFB_ReqT request) { +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) { int error, doClose = 0, doQuiet = 0, doToggle = 0, doMute = -1; long mute, volume; - json_object *doVolume, *responseJ, *argsJ = afb_request_json(request); + 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; + snd_ctl_t *ctlDev = NULL; CtlSourceT *source = alloca(sizeof (CtlSourceT)); source->uid = handle->verb; @@ -48,21 +62,22 @@ static void StreamApiVerbCB(AFB_ReqT request) { source->request = NULL; source->context = NULL; - error = wrap_json_unpack(argsJ, "{s?b s?b,s?b,s?b,s?o !}" + error = wrap_json_unpack(argsJ, "{s?b s?b,s?b,s?b,s?o,s?o !}" , "quiet", &doQuiet , "close", &doClose , "mute", &doMute , "toggle", &doToggle - , "volume", &doVolume + , "volume", &volumeJ + , "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->loop->cardid); + ctlDev = AlsaCtlOpenCtl(source, handle->mixer->frontend->cardid); if (!ctlDev) { - AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to open sndcard=%s", handle->mixer->loop->cardid); + AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to open sndcard=%s", handle->mixer->frontend->cardid); goto OnErrorExit; } @@ -72,24 +87,24 @@ static void StreamApiVerbCB(AFB_ReqT request) { } if (doToggle) { - error += AlsaCtlNumidGetLong(source, ctlDev, handle->streams->mute, &mute); - error += AlsaCtlNumidSetLong(source, ctlDev, handle->streams->mute, !mute); + error += AlsaCtlNumidGetLong(source, ctlDev, handle->stream->mute, &mute); + error += AlsaCtlNumidSetLong(source, ctlDev, handle->stream->mute, !mute); } - if (doVolume) { + if (volumeJ) { long curvol, newvol; const char*volString; - error = AlsaCtlNumidGetLong(source, ctlDev, handle->streams->volume, &curvol); + error = AlsaCtlNumidGetLong(source, ctlDev, handle->stream->volume, &curvol); if (error) { - AFB_ReqFailF(request, "invalid-numid", "Fail to set volume numid=%d value=%ld", handle->streams->volume, volume); + AFB_ReqFailF(request, "invalid-numid", "Fail to set volume numid=%d value=%ld", handle->stream->volume, volume); goto OnErrorExit; } - switch (json_object_get_type(doVolume)) { + switch (json_object_get_type(volumeJ)) { case json_type_string: - volString = json_object_get_string(doVolume); + volString = json_object_get_string(volumeJ); switch (volString[0]) { case '+': sscanf(&volString[1], "%ld", &newvol); @@ -101,30 +116,38 @@ static void StreamApiVerbCB(AFB_ReqT request) { newvol = curvol - newvol; break; default: - AFB_ReqFailF(request, "not-integer", "relative volume should start by '+|-' value=%s", json_object_get_string(doVolume)); + AFB_ReqFailF(request, "not-integer", "relative volume should start by '+|-' value=%s", json_object_get_string(volumeJ)); goto OnErrorExit; } break; case json_type_int: - newvol = json_object_get_int(doVolume); + newvol = json_object_get_int(volumeJ); break; default: - AFB_ReqFailF(request, "not-integer", "volume should be string or integer value=%s", json_object_get_string(doVolume)); + AFB_ReqFailF(request, "not-integer", "volume should be string or integer value=%s", json_object_get_string(volumeJ)); goto OnErrorExit; } - error = AlsaCtlNumidSetLong(source, ctlDev, handle->streams->volume, newvol); + error = AlsaCtlNumidSetLong(source, ctlDev, 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 (error) { - AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volume numid=%d value=%ld", handle->streams->volume, newvol); + AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volram numid=%d value=%s", handle->stream->volume, json_object_get_string(rampJ)); goto OnErrorExit; } } if (doMute != -1) { - error = AlsaCtlNumidSetLong(source, ctlDev, handle->streams->mute, !mute); + error = AlsaCtlNumidSetLong(source, ctlDev, handle->stream->mute, !mute); if (error) { - AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volume numid=%d value=%d", handle->streams->volume, !mute); + AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volume numid=%d value=%d", handle->stream->volume, !mute); goto OnErrorExit; } } @@ -132,8 +155,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->streams->volume, &volume); - error += AlsaCtlNumidGetLong(source, ctlDev, handle->streams->mute, &mute); + error += AlsaCtlNumidGetLong(source, ctlDev, handle->stream->volume, &volume); + error += AlsaCtlNumidGetLong(source, ctlDev, handle->stream->mute, &mute); if (error) { AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to get stream numids volume=%ld mute=%ld", volume, mute); goto OnErrorExit; @@ -151,7 +174,7 @@ OnErrorExit: } -STATIC int ProcessOneStream(CtlSourceT *source, json_object *streamJ, AlsaSndStreamT *stream) { +STATIC int ProcessOneStream(CtlSourceT *source, json_object *streamJ, AlsaLoopStreamT *stream) { int error; json_object *paramsJ = NULL; @@ -160,13 +183,15 @@ STATIC int ProcessOneStream(CtlSourceT *source, json_object *streamJ, AlsaSndStr stream->mute = 0; stream->info = NULL; - error = wrap_json_unpack(streamJ, "{ss,s?s,ss,s?i,s?b,s?o !}" + error = wrap_json_unpack(streamJ, "{ss,s?s,ss,s?i,s?b,s?o,s?s !}" , "uid", &stream->uid , "info", &stream->info , "zone", &stream->zone , "volume", &stream->volume , "mute", stream->mute - , "params", ¶msJ); + , "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)); goto OnErrorExit; @@ -195,9 +220,9 @@ OnErrorExit: return -1; } -PUBLIC int SndStreams(CtlSourceT *source, json_object *argsJ, json_object **responseJ) { +PUBLIC int LoopStreams(CtlSourceT *source, json_object *argsJ, json_object **responseJ) { SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; - AlsaSndStreamT *sndStream; + AlsaLoopStreamT *loopStream; int error; long value; size_t count; @@ -205,37 +230,37 @@ PUBLIC int SndStreams(CtlSourceT *source, json_object *argsJ, json_object **resp assert(mixerHandle); // assert static/global softmixer handle get requited info - AlsaSndLoopT *ctlLoop = mixerHandle->loop; + AlsaSndLoopT *ctlLoop = mixerHandle->frontend; if (!ctlLoop) { - AFB_ApiError(source->api, "SndStreams: mixer=%s No Loop found [should register snd_loop first]", mixerHandle->uid); + AFB_ApiError(source->api, "LoopStreams: mixer=%s No Loop found [should Registry snd_loop first]", mixerHandle->uid); goto OnErrorExit; } switch (json_object_get_type(argsJ)) { case json_type_object: count = 1; - sndStream = calloc(count + 1, sizeof (AlsaSndStreamT)); - error = ProcessOneStream(source, argsJ, &sndStream[0]); + loopStream = calloc(count + 1, sizeof (AlsaLoopStreamT)); + error = ProcessOneStream(source, argsJ, &loopStream[0]); if (error) { - AFB_ApiError(source->api, "SndStreams: mixer=%s invalid stream= %s", mixerHandle->uid, json_object_get_string(argsJ)); + AFB_ApiError(source->api, "LoopStreams: mixer=%s invalid stream= %s", mixerHandle->uid, json_object_get_string(argsJ)); goto OnErrorExit; } break; case json_type_array: count = json_object_array_length(argsJ); - sndStream = calloc(count + 1, sizeof (AlsaSndStreamT)); + loopStream = calloc(count + 1, sizeof (AlsaLoopStreamT)); for (int idx = 0; idx < count; idx++) { - json_object *sndStreamJ = json_object_array_get_idx(argsJ, idx); - error = ProcessOneStream(source, sndStreamJ, &sndStream[idx]); + json_object *loopStreamJ = json_object_array_get_idx(argsJ, idx); + error = ProcessOneStream(source, loopStreamJ, &loopStream[idx]); if (error) { - AFB_ApiError(source->api, "sndstreams: mixer=%s invalid stream= %s", mixerHandle->uid, json_object_get_string(sndStreamJ)); + AFB_ApiError(source->api, "loopstreams: mixer=%s invalid stream= %s", mixerHandle->uid, json_object_get_string(loopStreamJ)); goto OnErrorExit; } } break; default: - AFB_ApiError(source->api, "SndStreams: mixer=%s invalid argsJ= %s", mixerHandle->uid, json_object_get_string(argsJ)); + AFB_ApiError(source->api, "LoopStreams: mixer=%s invalid argsJ= %s", mixerHandle->uid, json_object_get_string(argsJ)); goto OnErrorExit; } @@ -243,14 +268,14 @@ PUBLIC int SndStreams(CtlSourceT *source, json_object *argsJ, json_object **resp // return stream data to application as a json array *responseJ = json_object_new_array(); - for (int idx = 0; sndStream[idx].uid != NULL; idx++) { + for (int idx = 0; loopStream[idx].uid != NULL; idx++) { json_object *streamJ, *paramsJ; // Search for a free loop capture device - AFB_ApiNotice(source->api, "SndStreams: mixer=%s stream=%s Start", mixerHandle->uid, (char*) sndStream[idx].uid); + AFB_ApiNotice(source->api, "LoopStreams: mixer=%s stream=%s Start", mixerHandle->uid, (char*) loopStream[idx].uid); ctlLoop->scount--; if (ctlLoop->scount < 0) { - AFB_ApiError(source->api, "SndStreams: mixer=%s stream=%s no more subdev avaliable in loopback=%s", mixerHandle->uid, sndStream[idx].uid, ctlLoop->uid); + AFB_ApiError(source->api, "LoopStreams: mixer=%s stream=%s no more subdev avaliable in loopback=%s", mixerHandle->uid, loopStream[idx].uid, ctlLoop->uid); goto OnErrorExit; } @@ -266,58 +291,58 @@ PUBLIC int SndStreams(CtlSourceT *source, json_object *argsJ, json_object **resp error = AlsaPcmConf(source, captureDev, &playbackDev->params); if (error) goto OnErrorExit; - // Register capture PCM for active/pause event + // Registry capture PCM for active/pause event if (captureDev->numid) { - error = AlsaCtlRegister(source, captureDev, captureDev->numid); + error = AlsaCtlRegister(source, mixerHandle, 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, "SndStreams: mixer=%s [pcm=%s] fail attache sndcard", mixerHandle->uid, captureDev->cardid); + AFB_ApiError(source->api, "LoopStreams: mixer=%s [pcm=%s] fail attache sndcard", mixerHandle->uid, captureDev->cardid); goto OnErrorExit; } - // create mute control and register it as pause/resume ctl) + // create mute control and Registry it as pause/resume ctl) char runName[ALSA_CARDID_MAX_LEN]; - snprintf(runName, sizeof (runName), "run-%s", sndStream[idx].uid); + snprintf(runName, sizeof (runName), "run-%s", loopStream[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; + int pauseNumid = AlsaCtlCreateControl(source, ctlDev, playbackDev, runName, 1, 0, 1, 1, loopStream[idx].mute); + if (pauseNumid <= 0) goto OnErrorExit; - // register mute/unmute as a pause/resume control - error = AlsaCtlRegister(source, captureDev, runNumid); + // Registry mute/unmute as a pause/resume control + error = AlsaCtlRegister(source, mixerHandle, 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", sndStream[idx].uid); - AlsaPcmInfoT *streamPcm = AlsaCreateSoftvol(source, &sndStream[idx], captureDev, volName, VOL_CONTROL_MAX, 0); + 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, "SndStreams: mixer=%s%s(pcm) fail to create stream", mixerHandle->uid, sndStream[idx].uid); + AFB_ApiError(source->api, "LoopStreams: mixer=%s%s(pcm) fail to create stream", mixerHandle->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, sndStream[idx].volume); + 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", sndStream[idx].uid); + // snprintf(rateName, sizeof (rateName), "rate-%s", loopStream[idx].uid); // AlsaPcmInfoT *ratePcm= AlsaCreateRate(source, rateName, streamPcm, 1); // if (!ratePcm) { - // AFB_ApiError(source->api, "SndStreams: mixer=%s%s(pcm) fail to create rate converter", sndStream[idx].uid); + // 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, sndStream[idx].uid, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + error = snd_pcm_open(&streamPcm->handle, loopStream[idx].uid, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (error) { - AFB_ApiError(source->api, "SndStreams: mixer=%s%s(pcm) fail to open capture", mixerHandle->uid, sndStream[idx].uid); + AFB_ApiError(source->api, "LoopStreams: mixer=%s%s(pcm) fail to open capture", mixerHandle->uid, loopStream[idx].uid); goto OnErrorExit; } @@ -325,18 +350,20 @@ PUBLIC int SndStreams(CtlSourceT *source, json_object *argsJ, json_object **resp captureDev->ccount = streamPcm->ccount; streamPcm->params.channels = streamPcm->ccount; - // start stream pcm copy (at this both capture & sink pcm should be open) - error = AlsaPcmCopy(source, captureDev, streamPcm, &streamPcm->params); + // 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, mixerHandle, 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_ApiError(source->api, "SndStreams: mixer=%s [capture=%s] fail to pause error=%s", mixerHandle->uid, captureDev->cardid, snd_strerror(error)); - goto OnErrorExit; + AFB_ApiWarning(source->api, "LoopStreams: mixer=%s [capture=%s] fail to pause error=%s", mixerHandle->uid, captureDev->cardid, snd_strerror(error)); } // prepare response for application @@ -344,45 +371,54 @@ PUBLIC int SndStreams(CtlSourceT *source, json_object *argsJ, json_object **resp error = AlsaByPathDevid(source, playbackDev); error += wrap_json_pack(¶msJ, "{si si si si}", "rate", streamPcm->params.rate, "channels", streamPcm->params.channels, "format", streamPcm->params.format, "access", streamPcm->params.access); - error += wrap_json_pack(&streamJ, "{ss ss si si so}", "uid", streamPcm->uid, "alsa", playbackDev->cardid, "volid", volNumid, "runid", runNumid, "params", paramsJ); + error += 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, "SndStreams: mixer=%s stream=%s fail to prepare response", mixerHandle->uid, captureDev->cardid); + AFB_ApiError(source->api, "LoopStreams: mixer=%s stream=%s fail to prepare response", mixerHandle->uid, captureDev->cardid); goto OnErrorExit; } // create a dedicated verb for this stream compose of mixeruid/streamuid apiHandleT *apiHandle = calloc(1, sizeof (apiHandleT)); char apiVerb[128]; - error = snprintf(apiVerb, sizeof (apiVerb), "%s/%s", mixerHandle->uid, sndStream[idx].uid); + error = snprintf(apiVerb, sizeof (apiVerb), "%s/%s", mixerHandle->uid, loopStream[idx].uid); if (error == sizeof (apiVerb)) { - AFB_ApiError(source->api, "SndStreams mixer=%s fail to register Stream API too long %s/%s", mixerHandle->uid, mixerHandle->uid, sndStream[idx].uid); + AFB_ApiError(source->api, "LoopStreams mixer=%s fail to Registry Stream API too long %s/%s", mixerHandle->uid, mixerHandle->uid, loopStream[idx].uid); return -1; } + // 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", mixerHandle->uid, loopStream[idx].uid, loopStream->ramp); + goto OnErrorExit; + } + } + apiHandle->mixer = mixerHandle; - apiHandle->streams = &sndStream[idx]; + apiHandle->stream= &loopStream[idx]; apiHandle->verb = strdup(apiVerb); - error = afb_dynapi_add_verb(source->api, apiHandle->verb, sndStream[idx].info, StreamApiVerbCB, apiHandle, NULL, 0); + error = afb_dynapi_add_verb(source->api, apiHandle->verb, loopStream[idx].info, StreamApiVerbCB, apiHandle, NULL, 0); if (error) { - AFB_ApiError(source->api, "SndStreams mixer=%s fail to register API verb=%s", mixerHandle->uid, apiHandle->verb); + AFB_ApiError(source->api, "LoopStreams mixer=%s fail to Registry API verb=%s", mixerHandle->uid, apiHandle->verb); return -1; } // free temporary resources snd_ctl_close(ctlDev); - sndStream[idx].volume = volNumid; - sndStream[idx].mute = runNumid; + 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, "SndStreams: mixer=%s stream=%s OK reponse=%s\n", mixerHandle->uid, streamPcm->uid, json_object_get_string(streamJ)); + AFB_ApiNotice(source->api, "LoopStreams: mixer=%s stream=%s OK reponse=%s\n", mixerHandle->uid, streamPcm->uid, json_object_get_string(streamJ)); } // save handle for further use - mixerHandle->streams = sndStream; + mixerHandle->streams = loopStream; return 0; OnErrorExit: diff --git a/plugins/alsa/alsa-api-zones.c b/plugins/alsa/alsa-api-zones.c index de834df..8f383df 100644 --- a/plugins/alsa/alsa-api-zones.c +++ b/plugins/alsa/alsa-api-zones.c @@ -24,7 +24,7 @@ // Fulup need to be cleanup with new controller version extern Lua2cWrapperT Lua2cWrap; -STATIC int ProcessOneChannel(CtlSourceT *source, const char* uid, json_object *channelJ, AlsaPcmChannels *channel) { +STATIC int ProcessOneChannel(CtlSourceT *source, const char* uid, json_object *channelJ, AlsaPcmChannelT *channel) { const char*channelUid; int error = wrap_json_unpack(channelJ, "{ss,si,s?i !}", "target", &channelUid, "channel", &channel->port); @@ -70,13 +70,13 @@ STATIC int ProcessOneZone(CtlSourceT *source, json_object *zoneJ, AlsaSndZoneT * switch (json_object_get_type(mappingJ)) { case json_type_object: count = 1; - zone->channels = calloc(count + 1, sizeof (AlsaPcmChannels)); + 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 (AlsaPcmChannels)); + 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]); @@ -103,7 +103,7 @@ PUBLIC int SndZones(CtlSourceT *source, json_object *argsJ) { assert(mixerHandle); if (mixerHandle->routes) { - AFB_ApiError(source->api, "SndZones: mixer=%s Zones already registered %s", mixerHandle->uid, json_object_get_string(argsJ)); + AFB_ApiError(source->api, "SndZones: mixer=%s Zones already Registryed %s", mixerHandle->uid, json_object_get_string(argsJ)); goto OnErrorExit; } @@ -135,7 +135,7 @@ PUBLIC int SndZones(CtlSourceT *source, json_object *argsJ) { goto OnErrorExit; } - // register routed into global softmixer handle + // Registry routed into global softmixer handle mixerHandle->routes= calloc(count + 1, sizeof (AlsaPcmInfoT*)); // instantiate one route PCM per zone with multi plugin as slave diff --git a/plugins/alsa/alsa-core-ctl.c b/plugins/alsa/alsa-core-ctl.c index 1749679..4ba871a 100644 --- a/plugins/alsa/alsa-core-ctl.c +++ b/plugins/alsa/alsa-core-ctl.c @@ -36,21 +36,9 @@ typedef struct { char* info; snd_ctl_t *ctlDev; sd_event *sdLoop; + RegistryHandleT *registry; } SubscribeHandleT; -typedef struct { - AlsaPcmInfoT *pcm; - int numid; -} SubStreamT; - -typedef struct { - SubStreamT stream[MAX_AUDIO_STREAMS + 1]; - int count; - snd_ctl_t *ctlDev; -} AudioStreamHandleT; - -static AudioStreamHandleT AudioStreamHandle; - PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNumidElemId(CtlSourceT *source, snd_ctl_t* ctlDev, int numid) { char string[32]; @@ -513,18 +501,30 @@ STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, v error = CtlElemIdGetNumid(subscribeHandle->api, subscribeHandle->ctlDev, elemId, &numid); if (error) goto OnErrorExit; - for (idx = 0; idx < AudioStreamHandle.count; idx++) { - if (AudioStreamHandle.stream[idx].numid == numid) { - const char *pcmName = AudioStreamHandle.stream[idx].pcm->cardid; - snd_pcm_pause(AudioStreamHandle.stream[idx].pcm->handle, !value); - AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d pcm=%s pause=%d numid=%d", subscribeHandle->info, subscribeHandle->tid, pcmName, !value, numid); + 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); + 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); + 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); + } break; } } - if (idx == AudioStreamHandle.count) { + 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 (ignored)", subscribeHandle->info, subscribeHandle->tid, cardName, numid); + AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d card=%s numid=%d (unknown)", subscribeHandle->info, subscribeHandle->tid, cardName, numid); } OnSuccessExit: @@ -577,7 +577,7 @@ OnErrorExit: return NULL; } -PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t * ctlDev) { +PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t * ctlDev, RegistryHandleT *registry) { int error; char string [32]; struct pollfd pfds; @@ -586,6 +586,7 @@ PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t * ctlDev) { subscribeHandle->api = source->api; subscribeHandle->ctlDev = ctlDev; subscribeHandle->info = "ctlEvt"; + subscribeHandle->registry= registry; // subscribe for sndctl events attached to cardid if ((error = snd_ctl_subscribe_events(ctlDev, 1)) < 0) { @@ -606,7 +607,7 @@ PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t * ctlDev) { goto OnErrorExit; } - // register sound event to binder main loop + // 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; @@ -624,30 +625,32 @@ OnErrorExit: return -1; } -PUBLIC int AlsaCtlRegister(CtlSourceT *source, AlsaPcmInfoT *pcm, int numid) { +PUBLIC int AlsaCtlRegister(CtlSourceT *source, SoftMixerHandleT *mixer, AlsaPcmInfoT *pcm, RegistryNumidT type, int numid) { + RegistryHandleT *registry= mixer->frontend->registry; - int count = AudioStreamHandle.count; + 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); goto OnErrorExit; } // If 1st registration then open a dev control channel to recieve events - if (!AudioStreamHandle.ctlDev) { + 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); + AlsaCtlSubscribe(source, ctlDev, registry); } // store PCM in order to pause/resume depending on event - AudioStreamHandle.stream[count].pcm = pcm; - AudioStreamHandle.stream[count].numid = numid; - AudioStreamHandle.count++; + registry->stream[count].pcm = pcm; + registry->stream[count].numid = numid; + registry->stream[count].type = type; + registry->count++; return 0; diff --git a/plugins/alsa/alsa-core-pcm.c b/plugins/alsa/alsa-core-pcm.c index bc676ef..ad8e842 100644 --- a/plugins/alsa/alsa-core-pcm.c +++ b/plugins/alsa/alsa-core-pcm.c @@ -27,22 +27,7 @@ for the specific language governing permissions and #include "alsa-softmixer.h" #include <pthread.h> #include <sys/syscall.h> - - -typedef struct { - snd_pcm_t *pcmIn; - snd_pcm_t *pcmOut; - AFB_ApiT api; - sd_event_source* evtsrc; - void* buffer; - size_t frameSize; - unsigned int frameCount; - unsigned int channels; - sd_event *sdLoop; - pthread_t thread; - int tid; - char* info; -} AlsaPcmCopyHandleT; +#include <sched.h> STATIC int AlsaPeriodSize(snd_pcm_format_t pcmFormat) { int pcmSampleSize; @@ -243,7 +228,7 @@ STATIC int AlsaPcmReadCB(sd_event_source* src, int fd, uint32_t revents, void* u AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PCM=%s Loosing frames=%ld", ALSA_PCM_UID(pcmCopyHandle->pcmOut, string), (framesIn - framesOut)); goto ExitOnSuccess; } - + return 0; // Cannot handle error in callback @@ -275,7 +260,7 @@ static void *LoopInThread(void *handle) { pthread_exit(0); } -PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pcmOut, AlsaPcmHwInfoT * opts) { +PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaLoopStreamT *loopStream, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pcmOut, AlsaPcmHwInfoT * opts) { char string[32]; struct pollfd *pcmInFds; int error; @@ -300,7 +285,7 @@ PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pc goto OnErrorExit; }; - AlsaPcmCopyHandleT *pcmCopyHandle = malloc(sizeof (AlsaPcmCopyHandleT)); + AlsaPcmCopyHandleT *pcmCopyHandle = &loopStream->copy; pcmCopyHandle->info = "pcmCpy"; pcmCopyHandle->pcmIn = pcmIn->handle; pcmCopyHandle->pcmOut = pcmOut->handle; @@ -309,6 +294,7 @@ PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pc pcmCopyHandle->frameSize = opts->channels * opts->sampleSize; pcmCopyHandle->frameCount = ALSA_BUFFER_FRAMES_COUNT; pcmCopyHandle->buffer = malloc(pcmCopyHandle->frameCount * pcmCopyHandle->frameSize); + // get FD poll descriptor for capture PCM int pcmInCount = snd_pcm_poll_descriptors_count(pcmCopyHandle->pcmIn); @@ -341,6 +327,14 @@ PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pc AFB_ApiError(source->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); + 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)); + } return 0; diff --git a/plugins/alsa/alsa-effect-ramp.c b/plugins/alsa/alsa-effect-ramp.c new file mode 100644 index 0000000..c861d0e --- /dev/null +++ b/plugins/alsa/alsa-effect-ramp.c @@ -0,0 +1,144 @@ +/* + * Copyright(C) 2018 "IoT.bzh" + * Author Fulup Ar Foll <fulup@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http : //www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License + +for the specific language governing permissions and + * limitations under the License. + * + * reference : + * https://github.com/zonque/simple-alsa-loop/blob/master/loop.c + * https://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html#a31 + * + */ + +#define _GNU_SOURCE // needed for vasprintf + +#include "alsa-softmixer.h" +#include <pthread.h> +#include <sys/syscall.h> + +typedef struct { + const char *uid; + long current; + long target; + int numid; + snd_ctl_t *ctlDev; + AlsaVolRampT *params; + sd_event_source *evtsrc; + CtlSourceT *source; + sd_event *sdLoop; +} VolRampHandleT; + +STATIC int VolRampTimerCB(sd_event_source* source, uint64_t timer, void* handle) { + VolRampHandleT *rampHandle = (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; + } + + // RampUp + if (rampHandle->current < rampHandle->target) { + rampHandle->current = rampHandle->current + rampHandle->params->stepUp; + if (rampHandle->current > rampHandle->target) rampHandle->current = rampHandle->target; + } + + error = AlsaCtlNumidSetLong(rampHandle->source, rampHandle->ctlDev, rampHandle->numid, rampHandle->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 { + // 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); + } + + return 0; + +OnErrorExit: + AFB_ApiWarning(rampHandle->source->api, "VolRampTimerCB stream=%s numid=%d value=%ld", rampHandle->uid, rampHandle->numid, rampHandle->current); + sd_event_source_unref(source); // abandon volRamp + return -1; +} + +PUBLIC int AlsaVolRampApply(CtlSourceT *source, AlsaSndLoopT *frontend, AlsaLoopStreamT *stream, AlsaVolRampT *ramp, json_object *volumeJ) { + long curvol, newvol; + const char*volString; + int error; + uint64_t usec; + + snd_ctl_t *ctlDev =AlsaCtlOpenCtl(source, frontend->cardid); + if (!ctlDev) goto OnErrorExit; + + error = AlsaCtlNumidGetLong(source, ctlDev, stream->volume, &curvol); + if (error) { + AFB_ApiError(source->api,"AlsaVolRampApply:%s(stream) Fail to get volume numid=%d", stream->uid, stream->volume); + goto OnErrorExit; + } + + switch (json_object_get_type(volumeJ)) { + case json_type_string: + volString = json_object_get_string(volumeJ); + switch (volString[0]) { + case '+': + sscanf(&volString[1], "%ld", &newvol); + newvol = curvol + newvol; + break; + + case '-': + sscanf(&volString[1], "%ld", &newvol); + newvol = curvol - 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; + } + break; + case json_type_int: + newvol = json_object_get_int(volumeJ); + break; + default: + AFB_ApiError(source->api,"AlsaVolRampApply:%s(stream) volume should be string or integer value=%s", stream->uid, json_object_get_string(volumeJ)); + 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; + + // 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); + + return 0; + +OnErrorExit: + return -1; +} diff --git a/plugins/alsa/alsa-plug-multi.c b/plugins/alsa/alsa-plug-multi.c index 0dd417d..c28508c 100644 --- a/plugins/alsa/alsa-plug-multi.c +++ b/plugins/alsa/alsa-plug-multi.c @@ -34,7 +34,7 @@ PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmUid, int AlsaPcmInfoT* pcmSlaves=mixerHandle->backend; if (!pcmSlaves) { - AFB_ApiError(source->api, "AlsaCreateMulti: No Sound Card find [should register snd_cards first]"); + AFB_ApiError(source->api, "AlsaCreateMulti: No Sound Card find [should Registry snd_cards first]"); goto OnErrorExit; } @@ -52,7 +52,7 @@ PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmUid, int // loop on sound card to include into multi for (int idx = 0; pcmSlaves[idx].uid != NULL; idx++) { AlsaPcmInfoT* sndcard=&pcmSlaves[idx]; - AlsaPcmChannels *channels = sndcard->channels; + AlsaPcmChannelT *channels = sndcard->channels; for (channelIdx=0; channels[channelIdx].uid != NULL; channelIdx++) { char idxS[4]; // 999 channel should be more than enough diff --git a/plugins/alsa/alsa-plug-route.c b/plugins/alsa/alsa-plug-route.c index d2ca223..f254c68 100644 --- a/plugins/alsa/alsa-plug-route.c +++ b/plugins/alsa/alsa-plug-route.c @@ -28,7 +28,7 @@ STATIC int CardChannelByUid(CtlSourceT *source, AlsaPcmInfoT *pcmBackend, const // search for channel within all sound card backend (channel port target is computed by order) int targetIdx=0; for (int cardIdx = 0; pcmBackend[cardIdx].uid != NULL; cardIdx++) { - AlsaPcmChannels *channels = pcmBackend[cardIdx].channels; + AlsaPcmChannelT *channels = pcmBackend[cardIdx].channels; if (!channels) { AFB_ApiError(source->api, "CardChannelByUid: No Backend card=%s [should declare channels]", pcmBackend[cardIdx].uid); goto OnErrorExit; @@ -60,7 +60,7 @@ PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone, int 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 register snd_cards first]" + AFB_ApiError(source->api, "AlsaCreateRoute: mixer=%s zone(%s)(zone) No Sound Card Ctl find [should Registry snd_cards first]" , mixerHandle->uid, zone->uid); goto OnErrorExit; } diff --git a/plugins/alsa/alsa-plug-vol.c b/plugins/alsa/alsa-plug-vol.c index 09bc51a..cb764ef 100644 --- a/plugins/alsa/alsa-plug-vol.c +++ b/plugins/alsa/alsa-plug-vol.c @@ -25,7 +25,7 @@ ALSA_PLUG_PROTO(softvol); // stream uses solftvol plugin STATIC AlsaPcmInfoT* SlaveZoneByUid(CtlSourceT *source, AlsaPcmInfoT **pcmZones, const char *uid) { AlsaPcmInfoT *slaveZone= NULL; - // Loop on every registered zone pcm and extract (cardid) from (uid) + // 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]; @@ -36,7 +36,7 @@ STATIC AlsaPcmInfoT* SlaveZoneByUid(CtlSourceT *source, AlsaPcmInfoT **pcmZones return NULL; } -PUBLIC AlsaPcmInfoT* AlsaCreateSoftvol(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *ctlControl, const char* ctlName, int max, int open) { +PUBLIC AlsaPcmInfoT* AlsaCreateSoftvol(CtlSourceT *source, AlsaLoopStreamT *stream, AlsaPcmInfoT *ctlControl, const char* ctlName, int max, int open) { SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; snd_config_t *streamConfig, *elemConfig, *slaveConfig, *controlConfig,*pcmConfig; int error = 0; @@ -45,16 +45,16 @@ PUBLIC AlsaPcmInfoT* AlsaCreateSoftvol(CtlSourceT *source, AlsaSndStreamT *strea assert (mixerHandle); // assert static/global softmixer handle get requited info - AlsaSndLoopT *ctlLoop = mixerHandle->loop; + AlsaSndLoopT *ctlLoop = mixerHandle->frontend; if (!ctlLoop) { - AFB_ApiError(source->api, "AlsaCreateSoftvol:%s(stream) No Loop found [should register snd_loop first]",stream->uid); + 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; if (!pcmZones) { - AFB_ApiError(source->api, "AlsaCreateSoftvol:%s(stream) No Zone found [should register snd_zones first]", stream->uid); + AFB_ApiError(source->api, "AlsaCreateSoftvol:%s(stream) No Zone found [should Registry snd_zones first]", stream->uid); goto OnErrorExit; } diff --git a/plugins/alsa/alsa-softmixer.c b/plugins/alsa/alsa-softmixer.c index 4528087..b414e20 100644 --- a/plugins/alsa/alsa-softmixer.c +++ b/plugins/alsa/alsa-softmixer.c @@ -22,11 +22,11 @@ // Force Lua2cWrapper inclusion within already existing plugin -CTLP_LUA_REGISTER("alsa-softmixer") +CTLP_LUA_Registry("alsa-softmixer") // Call at initialisation time CTLP_ONLOAD(plugin, callbacks) { - AFB_ApiDebug(plugin->api, "SoftMixer Plugin Registered: uid='%s' 'info='%s'", plugin->uid, plugin->info); + AFB_ApiDebug(plugin->api, "SoftMixer Plugin Registryed: uid='%s' 'info='%s'", plugin->uid, plugin->info); return NULL; } diff --git a/plugins/alsa/alsa-softmixer.h b/plugins/alsa/alsa-softmixer.h index f5a82f0..a8d7af6 100644 --- a/plugins/alsa/alsa-softmixer.h +++ b/plugins/alsa/alsa-softmixer.h @@ -48,6 +48,7 @@ #define ALSA_CARDID_MAX_LEN 64 + #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) @@ -58,12 +59,39 @@ #define STATIC static #endif -// alsa-utils-bypath.c + +typedef enum { + FONTEND_NUMID_IGNORE, + FONTEND_NUMID_PAUSE, + FONTEND_NUMID_RUN +} RegistryNumidT; + +typedef struct { + snd_pcm_t *pcmIn; + snd_pcm_t *pcmOut; + AFB_ApiT api; + sd_event_source* evtsrc; + void* buffer; + size_t frameSize; + unsigned int frameCount; + unsigned int channels; + sd_event *sdLoop; + pthread_t thread; + int tid; + char* info; +} AlsaPcmCopyHandleT; + +typedef struct { + const char *uid; + int delay; // delay between volset in us + int stepDown; // linear % + int stepUp; // linear % +} AlsaVolRampT; typedef struct { const char*uid; int port; -} AlsaPcmChannels; +} AlsaPcmChannelT; typedef struct { unsigned int rate; @@ -83,17 +111,31 @@ typedef struct { int numid; int ccount; snd_pcm_t *handle; - AlsaPcmChannels *channels; + AlsaPcmChannelT *channels; AlsaPcmHwInfoT params; } AlsaPcmInfoT; typedef struct { const char *uid; snd_pcm_stream_t type; - AlsaPcmChannels *channels; + AlsaPcmChannelT *channels; AlsaPcmInfoT *pcm; } AlsaSndZoneT; + +typedef struct { + AlsaPcmInfoT *pcm; + int numid; + RegistryNumidT type; +} RegistryStreamT; + +typedef struct { + RegistryStreamT stream[MAX_AUDIO_STREAMS + 1]; + int count; + snd_ctl_t *ctlDev; +} RegistryHandleT; + + typedef struct { const char *uid; const char *devpath; @@ -103,26 +145,30 @@ typedef struct { int capture; int scount; AlsaPcmInfoT *subdevs; + AlsaVolRampT *ramps; + RegistryHandleT *registry; } AlsaSndLoopT; typedef struct { const char *uid; const char *info; const char *zone; + const char *ramp; int volume; int mute; AlsaPcmInfoT *pcm; AlsaPcmHwInfoT params; -} AlsaSndStreamT; + AlsaPcmCopyHandleT copy; +} AlsaLoopStreamT; typedef struct { const char *uid; const char *info; - AlsaSndLoopT *loop; + AlsaSndLoopT *frontend; AlsaPcmInfoT *backend; AlsaPcmInfoT *multiPcm; AlsaPcmInfoT **routes; - AlsaSndStreamT *streams; + AlsaLoopStreamT *streams; } SoftMixerHandleT; // alsa-utils-bypath.c @@ -147,8 +193,8 @@ PUBLIC char *AlsaDumpCtlUid(snd_ctl_t *ctlHandle, char *buffer, size_t len); 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 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); @@ -160,13 +206,13 @@ PUBLIC int CtlElemIdGetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t * // alsa-core-pcm.c PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *opts); -PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pcmOut, AlsaPcmHwInfoT *opts); +PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaLoopStreamT *loopStream, 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, 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, AlsaSndStreamT *stream, AlsaPcmInfoT *ctlControl, const char* ctlName, int max, 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); // alsa-api-* @@ -174,6 +220,9 @@ PUBLIC int ProcessSndParams(CtlSourceT *source, const char* uid, json_object *pa PUBLIC int SndFrontend (CtlSourceT *source, json_object *argsJ); PUBLIC int SndBackend (CtlSourceT *source, json_object *argsJ); PUBLIC int SndZones (CtlSourceT *source, json_object *argsJ); -PUBLIC int SndStreams(CtlSourceT *source, json_object *argsJ, json_object **responseJ); +PUBLIC int LoopStreams(CtlSourceT *source, json_object *argsJ, json_object **responseJ); + +// alsa-effect-ramp.c +PUBLIC int AlsaVolRampApply(CtlSourceT *source, AlsaSndLoopT *frontend, AlsaLoopStreamT *stream, AlsaVolRampT *ramp, json_object *volumeJ); #endif
\ No newline at end of file |