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-sndstreams.c | 166 +++++++++++++++++++++++++------------ 1 file changed, 112 insertions(+), 54 deletions(-) (limited to 'plugins/alsa/alsa-api-sndstreams.c') 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; -- cgit 1.2.3-korg