/* * Copyright (C) 2018 "IoT.bzh" * Author Fulup Ar Foll * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #define _GNU_SOURCE // needed for vasprintf #include "alsa-softmixer.h" #include #include #include #include // Set stream volume control in % #define VOL_CONTROL_MAX 100 #define VOL_CONTROL_MIN 0 #define VOL_CONTROL_STEP 1 // Fulup need to be cleanup with new controller version extern Lua2cWrapperT Lua2cWrap; typedef struct { AlsaStreamAudioT *stream; SoftMixerT *mixer; AlsaVolRampT *ramp; AlsaSndCtlT *sndcard; snd_pcm_t *pcm; } apiHandleT; STATIC void StreamApiVerbCB(afb_req_t request) { apiHandleT *handle = (apiHandleT*) afb_req_get_vcbdata(request); int error, verbose = 0, doClose = 0, doToggle = 0, doMute = -1, doInfo = 0; long mute, volume, prevvol; json_object *volumeJ = NULL, *rampJ = NULL, *argsJ = afb_req_json(request); json_object *responseJ = NULL; SoftMixerT *mixer = handle->mixer; AlsaSndCtlT *sndcard = handle->sndcard; assert(mixer && sndcard); error = wrap_json_unpack(argsJ, "{s?b s?b,s?b,s?b,s?b,s?o,s?o !}" , "close", &doClose , "mute", &doMute , "toggle", &doToggle , "info", &doInfo , "verbose", &verbose , "volume", &volumeJ , "ramp", &rampJ ); if (error) { afb_req_fail_f(request, "syntax-error", "Missing 'close|mute|volume|verbose' args=%s", json_object_get_string(argsJ)); goto OnErrorExit; } if (verbose) responseJ = json_object_new_object(); if (doClose) { afb_req_fail_f(request, "internal-error", "(Fulup) Close action still to be done mixer=%s stream=%s", mixer->uid, handle->stream->uid); goto OnErrorExit; } if (doToggle) { error += AlsaCtlNumidGetLong(mixer, sndcard, handle->stream->mute, &mute); error += AlsaCtlNumidSetLong(mixer, sndcard, handle->stream->mute, !mute); if (error) { afb_req_fail_f(request, "invalid-numid", "Fail to set/get pause numid=%d", handle->stream->mute); goto OnErrorExit; } if (verbose) { json_object_object_add(responseJ, "mute", json_object_new_boolean(!mute)); } } if (doMute != -1) { error = AlsaCtlNumidSetLong(mixer, handle->sndcard, handle->stream->mute, doMute); if (error) { afb_req_fail_f(request, "StreamApiVerbCB", "Fail to set stream volume numid=%d value=%d", handle->stream->volume, !doMute); goto OnErrorExit; } if (verbose) { error += AlsaCtlNumidGetLong(mixer, handle->sndcard, handle->stream->mute, &mute); json_object_object_add(responseJ, "mute", json_object_new_boolean((json_bool) mute)); } } if (volumeJ) { long voltoset, newvol; const char*volString; error = AlsaCtlNumidGetLong(mixer, handle->sndcard, handle->stream->volume, &prevvol); if (error) { afb_req_fail_f(request, "invalid-numid", "Fail to get volume numid=%d value=%ld", handle->stream->volume, 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", &voltoset); voltoset = prevvol + voltoset; break; case '-': sscanf(&volString[1], "%ld", &voltoset); voltoset = prevvol - voltoset; break; default: error = sscanf(&volString[0], "%ld", &voltoset); if (error != 1) { afb_req_fail_f(request, "not-integer", "relative volume should start by '+|-' value=%s", json_object_get_string(volumeJ)); goto OnErrorExit; } } break; case json_type_int: voltoset = json_object_get_int(volumeJ); break; default: afb_req_fail_f(request, "not-integer", "volume should be string or integer value=%s", json_object_get_string(volumeJ)); goto OnErrorExit; } error = AlsaCtlNumidSetLong(mixer, handle->sndcard, handle->stream->volume, voltoset); if (error) { afb_req_fail_f(request, "StreamApiVerbCB", "Fail to set stream volume numid=%d value=%ld", handle->stream->volume, voltoset); goto OnErrorExit; } error = AlsaCtlNumidGetLong(mixer, handle->sndcard, handle->stream->volume, &newvol); if (error) { afb_req_fail_f(request, "invalid-numid", "Fail to get volume numid=%d value=%ld", handle->stream->volume, volume); goto OnErrorExit; } if (verbose) { json_object_object_add(responseJ, "volnew", json_object_new_int((int) newvol)); json_object_object_add(responseJ, "volold", json_object_new_int((int) prevvol)); } } if (rampJ) { if (verbose) { error = AlsaCtlNumidGetLong(mixer, handle->sndcard, handle->stream->volume, &prevvol); json_object_object_add(responseJ, "volold", json_object_new_int((int) prevvol)); } error += AlsaVolRampApply(mixer, handle->sndcard, handle->stream, rampJ); if (error) { afb_req_fail_f(request, "StreamApiVerbCB", "Fail to set stream volram numid=%d value=%s", handle->stream->volume, json_object_get_string(rampJ)); goto OnErrorExit; } } if (doInfo) { json_object_put(responseJ); // free default response. error += AlsaCtlNumidGetLong(mixer, handle->sndcard, handle->stream->volume, &volume); error += AlsaCtlNumidGetLong(mixer, handle->sndcard, handle->stream->mute, &mute); if (error) { afb_req_fail_f(request, "StreamApiVerbCB", "Fail to get stream numids volume=%ld mute=%ld", volume, mute); goto OnErrorExit; } wrap_json_pack(&responseJ, "{si,sb}", "volume", volume, "mute", !mute); } afb_req_success(request, responseJ, NULL); return; OnErrorExit: return; } static void paramsOverride(SoftMixerT *mixer, AlsaStreamAudioT * destStream, const AlsaPcmHwInfoT * src) { if (!destStream->params || !src) return; AlsaPcmHwInfoT * dest = destStream->params; /* The stream always has the same number of channels as the source */ dest->channels = src->channels; if (dest->format != src->format) { AFB_API_NOTICE(mixer->api, "Stream %s overrides format to %d", destStream->uid, src->format); dest->format = src->format; strncpy(dest->formatString, src->formatString, SND_FORMAT_STRING_LEN ); } if (dest->access != src->access) { AFB_API_NOTICE(mixer->api, "Stream %s overrides access to %d", destStream->uid, src->access); dest->access = src->access; } if (dest->rate != src->rate) { AFB_API_NOTICE(mixer->api, "Stream %s overrides rate to %d", destStream->uid, src->rate); dest->rate = src->rate; } } STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT * stream) { int error; long value; AlsaSndLoopT *loop = NULL; AlsaPcmCtlT *streamPcm; AlsaSndCtlT *captureCard; AlsaDevInfoT *captureDev = alloca(sizeof (AlsaDevInfoT)); AlsaLoopSubdevT *loopDev; AlsaSndZoneT *zone = NULL; char *volSlaveId = NULL; char *playbackName = NULL; char *runName = NULL; char *volName = NULL; int pauseNumid = 0; int volNumid = 0; int device, subdev; AFB_API_DEBUG(mixer->api, "NEW STREAM stream %s %s, source %s, sink %s, mute %d", uid, stream->uid, stream->source, stream->sink, stream->mute); loopDev = ApiLoopFindSubdev(mixer, stream->uid, stream->source, &loop); if (loopDev) { if (loop->avirt) { device = loopDev->index; subdev = 0; } else { // loop->avirt == false device = loop->capture; subdev = loopDev->index; } // create a valid PCM reference and try to open it. captureDev->devpath = NULL; captureDev->cardid = NULL; captureDev->cardidx = loop->sndcard->cid.cardidx; captureDev->device = device; captureDev->subdev = subdev; captureDev->pcmplug_params = NULL; captureCard = loop->sndcard; AFB_API_DEBUG(mixer->api, "%s: found loopdev %d,%d", __func__, loop->capture, loopDev->index); } else { // if capture UID is not present in loop search on known sources AFB_API_DEBUG(mixer->api,"%s: %s not found in loop, look in sources", __func__, stream->source); AlsaSndCtlT *sourceDev = ApiSourceFindSubdev(mixer, stream->source); if (sourceDev) { captureDev->devpath = NULL; captureDev->cardid = NULL; captureDev->cardidx = sourceDev->cid.cardidx; captureDev->device = sourceDev->cid.device; captureDev->subdev = sourceDev->cid.subdev; captureDev->pcmplug_params = sourceDev->cid.pcmplug_params; captureCard = sourceDev; AFB_API_DEBUG(mixer->api, "%s found capture for %s (plug %s, card %s)", __func__, uid, captureDev->pcmplug_params, sourceDev->cid.cardid); } else { AFB_API_ERROR(mixer->api, "%s: mixer=%s stream=%s: %s not found in loops/sources", __func__, mixer->uid, stream->uid, stream->source); goto OnErrorExit; } } stream->optional = captureCard->optional; if (!captureCard->ctl && captureCard->optional) { stream->noHwDetected = true; AFB_API_INFO(mixer->api,"%s: no detected capture device", __func__); goto done; } // check PCM is valid and get its full name AlsaPcmCtlT *capturePcm = AlsaByPathOpenPcmCtl(mixer, captureDev, SND_PCM_STREAM_CAPTURE); if (!capturePcm) { AFB_API_ERROR(mixer->api,"%s: Unable to open the capture PCM !", __func__); goto OnErrorExit; } paramsOverride(mixer, stream, captureCard->params); capturePcm->closeAtDeletion = true; capturePcm->mute = stream->mute; AFB_API_DEBUG(mixer->api,"%s: Capture PCM opened !", __func__); // Registry capturePcm PCM for active/pause event if (loopDev && loopDev->numid) { AFB_API_DEBUG(mixer->api, "%s: REGISTER active/pause", __func__); error = AlsaCtlRegister(mixer, captureCard, capturePcm, FONTEND_NUMID_RUN, loopDev->numid); if (error) goto OnErrorExit; } if (stream->sink) { // if zones exist then retrieve zone pcmid and channel count zone = ApiZoneGetByUid(mixer, stream->sink); if (!zone) { AFB_API_ERROR(mixer->api, "%s: mixer=%s stream=%s fail to find sink zone='%s'", __func__, mixer->uid, stream->uid, stream->sink); goto OnErrorExit; } // route PCM should have been create during zones attach phase. if (asprintf(&volSlaveId, "route-%s", zone->uid) == -1) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } } else if (stream->playback) { AlsaSndPcmT *playback = ApiSinkGetByUid(mixer, stream->playback); if (!playback) { AFB_API_ERROR(mixer->api, "%s: mixer=%s stream=%s fail to find sink playback='%s'", __func__, mixer->uid, stream->uid, stream->sink); goto OnErrorExit; } /* there is no dmix on pcmplugs */ if (playback->isPcmPlug) { if (asprintf(&volSlaveId, "%s", playback->sndcard->cid.pcmplug_params) == -1) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } } else { if (asprintf(&volSlaveId, "dmix-%s", playback->uid) == -1) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } } AFB_API_ERROR(mixer->api, "%s: Allocate a fake ZONE", __func__); // create a fake zone for rate converter selection zone=alloca(sizeof(AlsaSndZoneT)); zone->uid = playback->uid; ApiPcmParamsShow(mixer,"PLAYBACK to FAKE ZONE", playback->sndcard->params); zone->params = ApiPcmParamsDup(mixer, playback->sndcard->params); zone->ccount = playback->nbChannels; } // create mute control and Registry it as pause/resume ctl) if (asprintf(&runName, "pause-%s", stream->uid) == -1) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } AFB_API_DEBUG(mixer->api,"%s: create mute control...", __func__); pauseNumid = AlsaCtlCreateControl(mixer, captureCard, runName, 1, 0, 1, 1, stream->mute); if (pauseNumid <= 0) { AFB_API_ERROR(mixer->api, "%s: Failed to create pause control", __func__); goto OnErrorExit; } AFB_API_DEBUG(mixer->api,"%s: register mute control...", __func__); // Registry stop/play as a pause/resume control error = AlsaCtlRegister(mixer, captureCard, capturePcm, FONTEND_NUMID_PAUSE, pauseNumid); if (error) { AFB_API_ERROR(mixer->api, "%s: Failed to register pause control", __func__); goto OnErrorExit; } if (asprintf(&volName, "vol-%s", stream->uid) == -1) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } AFB_API_DEBUG(mixer->api,"%s: create softvol", __func__); // create stream and delay pcm opening until vol control is created streamPcm = AlsaCreateSoftvol(mixer, stream, volSlaveId, captureCard, volName, VOL_CONTROL_MAX, 0); if (!streamPcm) { AFB_API_ERROR(mixer->api, "%s failed to create soft volume PCM", __func__); goto OnErrorExit; } AFB_API_DEBUG(mixer->api,"%s: create softvol control", __func__); ApiPcmParamsShow(mixer, "Stream ", stream->params); ApiPcmParamsShow(mixer, "Zone", zone->params); // create volume control before softvol pcm is opened volNumid = AlsaCtlCreateControl(mixer, captureCard, volName, stream->params->channels, VOL_CONTROL_MIN, VOL_CONTROL_MAX, VOL_CONTROL_STEP, stream->volume); if (volNumid <= 0) { AFB_API_ERROR(mixer->api, "%s failed add volume control on capture card", __func__); goto OnErrorExit; } if ((zone->params->rate != stream->params->rate) || (zone->params->format != stream->params->format)) { AFB_API_NOTICE(mixer->api, "%s: Instantiate a RATE CONVERTER (stream [rate %d,%s(%d),%d channels], zone [rate %d,%s(%d), %d channels])", __func__, stream->params->rate, stream->params->formatString, stream->params->format, stream->params->channels, zone->params->rate, zone->params->formatString, zone->params->format, zone->params->channels); char *rateName; if (asprintf(&rateName, "rate-%s", stream->uid) == -1) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } streamPcm = AlsaCreateRate(mixer, stream, rateName, streamPcm, zone->params, 0); if (!streamPcm) { AFB_API_ERROR(mixer->api, "%s: fail to create rate converter", __func__); goto OnErrorExit; } playbackName = rateName; } else { AFB_API_NOTICE(mixer->api, "%s: no need for a converter", __func__); playbackName = (char*) streamPcm->cid.cardid; } streamPcm->isPcmPlug = zone->isPcmPlug; streamPcm->quirks = zone->quirks; AFB_API_DEBUG(mixer->api, "%s: Opening PCM PLAYBACK name %s (quirks=0x%x)", __func__, playbackName, streamPcm->quirks); // everything is now ready to open playback pcm in BLOCKING mode this time error = snd_pcm_open(&streamPcm->handle, playbackName, SND_PCM_STREAM_PLAYBACK, 0 /* will block*/ ); if (error) { AFB_API_ERROR(mixer->api, "%s: mixer=%s stream=%s fail to open playback PCM=%s; error=%s", __func__, mixer->uid, stream->uid, streamPcm->cid.cardid, snd_strerror(error)); goto OnErrorExit; } streamPcm->closeAtDeletion = true; // start stream pcm copy (at this both capturePcm & sink pcm should be open, we use output params to configure both in+outPCM) error = AlsaPcmCopyStart(mixer, stream, capturePcm, streamPcm, stream->params); if (error) { AFB_API_ERROR(mixer->api, "%s: Failed to launch copy", __func__); goto OnErrorExit; } AFB_API_DEBUG(mixer->api, "%s: register VOL ctrl", __func__); error = AlsaCtlRegister(mixer, captureCard, capturePcm, FONTEND_NUMID_IGNORE, volNumid); if (error) { AFB_API_ERROR(mixer->api, "%s: register control on capture", __func__); goto OnErrorExit; } // when using loopdev check if subdev is active or not to prevent thread from reading empty packet if (loopDev && loopDev->numid) { // retrieve active/pause control and set PCM status accordingly error = AlsaCtlNumidGetLong(mixer, captureCard, loopDev->numid, &value); if (error) goto OnErrorExit; // toggle pause/resume (should be done after pcm_start) if ((error = snd_pcm_pause(capturePcm->handle, !value)) < 0) { AFB_API_WARNING(mixer->api, "CreateOneStream: mixer=%s [capturePcm=%s] fail to pause error=%s", mixer->uid, captureDev->cardid, snd_strerror(error)); } } if (loop) { int device = (loop->avirt) ? captureDev->device : loop->playback; if (asprintf((char**) &stream->source, "hw:%d,%d,%d", captureDev->cardidx, device, capturePcm->cid.subdev) == -1) goto OnErrorExit; } else { if (asprintf((char**) &stream->source, "hw:%d,%d,%d", captureDev->cardidx, captureDev->device, captureDev->subdev) == -1) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } } // create a dedicated verb for this stream apiHandleT *apiHandle = calloc(1, sizeof (apiHandleT)); if (apiHandle == NULL) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } apiHandle->mixer = mixer; apiHandle->stream = stream; apiHandle->sndcard = captureCard; apiHandle->pcm = capturePcm->handle; // keep a reference for future cleanup stream->verbApiHandle = apiHandle; // replace stream volume/mute values with corresponding ctl control stream->volume = volNumid; stream->mute = pauseNumid; error = afb_api_add_verb(mixer->api, stream->verb, stream->info, StreamApiVerbCB, apiHandle, NULL, 0, 0); if (error) { AFB_API_ERROR(mixer->api, "%s mixer=%s fail to Register API verb stream=%s", __func__, mixer->uid, stream->uid); goto OnErrorExit; } // Debug Alsa Config //AlsaDumpElemConfig (source, "\n\nAlsa_Config\n------------\n", "pcm"); //AlsaDumpPcmInfo(source, "\n\nPcm_config\n-----------\n", streamPcm->handle); AFB_API_NOTICE(mixer->api, "%s: mixer=%s stream=%s CREATED", __func__, mixer->uid, stream->uid); done: return 0; OnErrorExit: free(volSlaveId); free(runName); free(volName); return -1; } STATIC AlsaStreamAudioT * AttachOneStream(SoftMixerT *mixer, const char *uid, const char *prefix, json_object * streamJ) { int error; json_object *paramsJ = NULL; AlsaStreamAudioT *stream = calloc(1, sizeof (AlsaStreamAudioT)); if (stream == NULL) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } // Make sure default runs stream->volume = ALSA_DEFAULT_PCM_VOLUME; stream->mute = 0; stream->info = NULL; stream->delayms = SMIXER_DEFLT_DELAYMS; error = wrap_json_unpack(streamJ, "{ss,s?s,s?s,s?s,s?s,s?s,s?i,s?b,s?o,s?s,s?i !}" , "uid", &stream->uid , "verb", &stream->verb , "info", &stream->info , "zone", &stream->sink , "playback", &stream->playback , "source", &stream->source , "volume", &stream->volume , "mute", &stream->mute , "params", ¶msJ , "ramp", &stream->ramp , "delayms", &stream->delayms ); if (error) { AFB_API_ERROR(mixer->api, "%s: hal=%s missing 'uid|[info]|zone|source||[volume]|[mute]|[params]' error=%s stream=%s", __func__, uid, wrap_json_get_error_string(error), json_object_get_string(streamJ)); goto OnErrorExit; } if (!stream->sink && !stream->playback) { AFB_API_ERROR(mixer->api, "%s: A stream must have a zone or a playback", __func__); goto OnErrorExit; } if (stream->sink && stream->playback) { AFB_API_ERROR(mixer->api, "%s: both a playback and a zone cannot be defined at the same time", __func__); goto OnErrorExit; } stream->params = ApiPcmSetParams(mixer, stream->uid, paramsJ); if (!stream->params) { AFB_API_ERROR(mixer->api, "%s: hal=%s stream=%s invalid params=%s", __func__, uid, stream->uid, json_object_get_string(paramsJ)); goto OnErrorExit; } // make sure remain valid even when json object is removed stream->uid = strdup(stream->uid); if (stream->uid == NULL) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } if (stream->sink) { stream->sink = strdup(stream->sink); if (stream->sink == NULL) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } } if (stream->source) { stream->source = strdup(stream->source); if (stream->source == NULL) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } } if (stream->playback) { stream->playback = strdup(stream->playback); if (stream->playback == NULL) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } } // Prefix verb with uid|prefix if (prefix) { if (stream->verb) { if (asprintf((char**) &stream->verb, "%s#%s", prefix, stream->verb) == -1) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } } else { if (asprintf((char**) &stream->verb, "%s#%s", prefix, stream->uid) == -1) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } } } else { if (!stream->verb) { stream->verb = strdup(stream->uid); if (stream->verb == NULL) { SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } } } // implement stream PCM with corresponding thread and controls error = CreateOneStream(mixer, uid, stream); if (error) { AFB_API_ERROR(mixer->api, "%s: failed to create stream", __func__); goto OnErrorExit; } return stream; OnErrorExit: free(stream); AFB_API_ERROR(mixer->api, "%s fail", __func__); return NULL; } static void streamDestroy(SoftMixerT * mixer, void * arg) { AlsaStreamAudioT * stream = (AlsaStreamAudioT*) arg; int error = 0; AFB_API_DEBUG(mixer->api, "%s... %s", __func__, stream->uid); if (stream->noHwDetected) goto freemem; error = afb_api_del_verb(mixer->api, stream->uid, (void**)stream->verbApiHandle); if (error) { AFB_API_DEBUG(mixer->api, "%s: failed to remove verb %s", __func__, stream->uid); } AlsaPcmCopyStop(mixer, stream->copy); freemem: free((char*)stream->uid); free((char*)stream->playback); free((char*)stream->ramp); free((char*)stream->sink); free((char*)stream->source); free((char*)stream->verb); ApiPcmDelParams(mixer, stream->params); cds_list_del(&stream->list); mixer->nbStreams--; free(stream); // uid is no available anymore at this position AFB_API_DEBUG(mixer->api, "%s... DONE !", __func__); } static AlsaStreamAudioT * streamCreate(SoftMixerT * mixer, const char * uid, const char * prefix, json_object * argsJ) { AlsaStreamAudioT * newStream = AttachOneStream(mixer, uid, prefix, argsJ); if (!newStream) { goto fail; } mixer->nbStreams++; cds_list_add_tail(&newStream->list, &mixer->streams.list); AlsaMixerTransactionObjectAdd(mixer->transaction, newStream, streamDestroy); fail: return newStream; } PUBLIC int ApiStreamAttach(SoftMixerT *mixer, afb_req_t request, const char * uid, const char *prefix, json_object * argsJ) { AFB_API_DEBUG(mixer->api, "%s: %s prefix %s", __func__, uid, prefix); AlsaStreamAudioT * newStream = NULL; if (mixer->nbLoops == 0) { AFB_API_ERROR(mixer->api, "%s: mixer=%s No Loop found [should Registry snd_loop first]", __func__, mixer->uid); goto fail; } if (mixer->nbStreams >= mixer->max.streams) { afb_req_fail_f(request, "too-small", "mixer=%s max stream=%d", mixer->uid, mixer->max.streams); goto fail; } switch (json_object_get_type(argsJ)) { long count; case json_type_object: newStream = streamCreate(mixer, uid, prefix, argsJ); if (!newStream) { afb_req_fail_f(request, "bad-stream", "mixer=%s invalid stream= %s", mixer->uid, json_object_get_string(argsJ)); goto fail; } if (newStream->noHwDetected) AlsaMixerTransactionObjectDelete(mixer->transaction, newStream, true); break; case json_type_array: count = json_object_array_length(argsJ); if (count > (mixer->max.streams - mixer->nbStreams)) { afb_req_fail_f(request, "too-small", "mixer=%s max stream=%d", mixer->uid, mixer->max.streams); goto fail; } for (int idx = 0; idx < count; idx++) { json_object *streamJ = json_object_array_get_idx(argsJ, idx); newStream = streamCreate(mixer, uid, prefix, streamJ); if (!newStream) { afb_req_fail_f(request, "bad-stream", "%s: mixer=%s invalid stream= %s", __func__, mixer->uid, json_object_get_string(streamJ)); goto fail; } if (newStream->noHwDetected) AlsaMixerTransactionObjectDelete(mixer->transaction, newStream, true); } break; default: afb_req_fail_f(request, "invalid-syntax", "mixer=%s streams invalid argsJ= %s", mixer->uid, json_object_get_string(argsJ)); goto fail; } return 0; fail: AFB_API_ERROR(mixer->api, "%s FAILED\n", __func__); return -1; }