From 7454d66bb47349418f8f65b8d7bec79039a2be32 Mon Sep 17 00:00:00 2001 From: Fulup Ar Foll Date: Fri, 18 May 2018 13:31:36 +0200 Subject: Implements volume ramping --- plugins/alsa/alsa-api-streams.c | 184 ++++++++++++++++++++++++---------------- 1 file changed, 110 insertions(+), 74 deletions(-) (limited to 'plugins/alsa/alsa-api-streams.c') 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: -- cgit 1.2.3-korg