aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/alsa/alsa-api-sndstreams.c
diff options
context:
space:
mode:
authorFulup Ar Foll <fulup@iot.bzh>2018-05-13 22:07:22 +0200
committerFulup Ar Foll <fulup@iot.bzh>2018-05-13 22:07:22 +0200
commit6f13e28ba698a2b0145acbb926b79cd569a31f44 (patch)
tree061e2daace484aea73200ab85db39b3fafeb95e4 /plugins/alsa/alsa-api-sndstreams.c
parent0eb15da6365910ba3f290e3254719fd412ae0155 (diff)
First testable version.
Mixing with volume and mute per audio role works.
Diffstat (limited to 'plugins/alsa/alsa-api-sndstreams.c')
-rw-r--r--plugins/alsa/alsa-api-sndstreams.c166
1 files changed, 112 insertions, 54 deletions
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", &paramsJ);
+ 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(&paramsJ, "{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;