diff options
Diffstat (limited to 'plugins/alsa/alsa-api-streams.c')
-rw-r--r-- | plugins/alsa/alsa-api-streams.c | 325 |
1 files changed, 250 insertions, 75 deletions
diff --git a/plugins/alsa/alsa-api-streams.c b/plugins/alsa/alsa-api-streams.c index 9a5c5c2..e35a8b7 100644 --- a/plugins/alsa/alsa-api-streams.c +++ b/plugins/alsa/alsa-api-streams.c @@ -19,7 +19,6 @@ #define _GNU_SOURCE // needed for vasprintf #include "alsa-softmixer.h" -#include "alsa-bluez.h" #include <string.h> #include <stdbool.h> @@ -184,6 +183,34 @@ 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_ApiNotice(mixer->api, "Stream %s overrides format to %d", destStream->uid, src->format); + dest->format = src->format; + dest->formatS = strdup(src->formatS); + } + + if (dest->access != src->access) { + AFB_ApiNotice(mixer->api, "Stream %s overrides access to %d", destStream->uid, src->access); + dest->access = src->access; + } + + if (dest->rate != src->rate) { + AFB_ApiNotice(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; @@ -192,7 +219,7 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT AlsaSndCtlT *captureCard; AlsaDevInfoT *captureDev = alloca(sizeof (AlsaDevInfoT)); AlsaLoopSubdevT *loopDev; - AlsaSndZoneT *zone; + AlsaSndZoneT *zone = NULL; char *volSlaveId = NULL; char *playbackName = NULL; char *runName = NULL; @@ -200,9 +227,9 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT int pauseNumid = 0; int volNumid = 0; - AFB_ApiInfo(mixer->api, - "%s, stream %s %s, source %s, sink %s, mute %d", - __func__,uid, stream->uid, stream->source, stream->sink, stream->mute); + AFB_ApiDebug(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) { @@ -215,13 +242,13 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT captureDev->pcmplug_params = NULL; captureCard = loop->sndcard; - AFB_ApiInfo(mixer->api, + AFB_ApiDebug(mixer->api, "%s: found loopdev %d,%d", __func__, loop->capture, loopDev->index); } else { - // if capture UID is not present in loop search on sources - AFB_ApiInfo(mixer->api,"%s: %s not found in loop, look in sources", __func__, uid); + // if capture UID is not present in loop search on known sources + AFB_ApiDebug(mixer->api,"%s: %s not found in loop, look in sources", __func__, uid); AlsaSndCtlT *sourceDev = ApiSourceFindSubdev(mixer, stream->source); if (sourceDev) { @@ -232,7 +259,8 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT captureDev->subdev = sourceDev->cid.subdev; captureDev->pcmplug_params = sourceDev->cid.pcmplug_params; captureCard = sourceDev; - AFB_ApiInfo(mixer->api, "%s found capture %s", __func__, uid); + AFB_ApiDebug(mixer->api, "%s found capture for %s (plug %s, card %s)", + __func__, uid, captureDev->pcmplug_params, sourceDev->cid.cardid); } else { AFB_ApiError(mixer->api, "%s: mixer=%s stream=%s not found in loops/sources", @@ -242,20 +270,28 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT } // check PCM is valid and get its full name - AlsaPcmCtlT *capturePcm = AlsaByPathOpenPcm(mixer, captureDev, SND_PCM_STREAM_CAPTURE); - if (!capturePcm) goto OnErrorExit; + AlsaPcmCtlT *capturePcm = AlsaByPathOpenPcmCtl(mixer, captureDev, SND_PCM_STREAM_CAPTURE); + if (!capturePcm) { + AFB_ApiError(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_ApiInfo(mixer->api,"%s: PCM opened !", __func__); + AFB_ApiDebug(mixer->api,"%s: Capture PCM opened !", __func__); // Registry capturePcm PCM for active/pause event if (loopDev && loopDev->numid) { + AFB_ApiDebug(mixer->api, "%s: REGISTER active/pause", __func__); error = AlsaCtlRegister(mixer, captureCard, capturePcm, FONTEND_NUMID_RUN, loopDev->numid); - if (error) goto OnErrorExit; + if (error) + goto OnErrorExit; } - if (mixer->zones[0]) { + if (stream->sink) { // if zones exist then retrieve zone pcmid and channel count zone = ApiZoneGetByUid(mixer, stream->sink); if (!zone) { @@ -266,11 +302,13 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT } // route PCM should have been create during zones attach phase. - if (asprintf(&volSlaveId, "route-%s", zone->uid) == -1) + if (asprintf(&volSlaveId, "route-%s", zone->uid) == -1) { + SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; + } - } else { - AlsaSndPcmT *playback = ApiSinkGetByUid(mixer, stream->sink); + } else if (stream->playback) { + AlsaSndPcmT *playback = ApiSinkGetByUid(mixer, stream->playback); if (!playback) { AFB_ApiError(mixer->api, "%s: mixer=%s stream=%s fail to find sink playback='%s'", @@ -278,26 +316,37 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT goto OnErrorExit; } - // retrieve channel count from route and push it to stream - if (asprintf(&volSlaveId, "dmix-%s", playback->uid) == -1) - 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_ApiError(mixer->api, "%s: Allocate a fake ZONE", __func__); + // create a fake zone for rate converter selection zone=alloca(sizeof(AlsaSndZoneT)); zone->uid= playback->uid; zone->params = playback->sndcard->params; - zone->ccount = playback->ccount; - } + zone->ccount = playback->nbChannels; - // retrieve channel count from route and push it to stream - - stream->params->channels = zone->ccount; + } // create mute control and Registry it as pause/resume ctl) - if (asprintf(&runName, "pause-%s", stream->uid) == -1) + if (asprintf(&runName, "pause-%s", stream->uid) == -1) { + SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; + } - AFB_ApiInfo(mixer->api,"%s: create mute control...", __func__); + AFB_ApiDebug(mixer->api,"%s: create mute control...", __func__); pauseNumid = AlsaCtlCreateControl(mixer, captureCard, runName, 1, 0, 1, 1, stream->mute); if (pauseNumid <= 0) { @@ -305,7 +354,7 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT goto OnErrorExit; } - AFB_ApiInfo(mixer->api,"%s: register mute control...", __func__); + AFB_ApiDebug(mixer->api,"%s: register mute control...", __func__); // Registry stop/play as a pause/resume control error = AlsaCtlRegister(mixer, captureCard, capturePcm, FONTEND_NUMID_PAUSE, pauseNumid); @@ -314,10 +363,12 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT goto OnErrorExit; } - if (asprintf(&volName, "vol-%s", stream->uid) == -1) + if (asprintf(&volName, "vol-%s", stream->uid) == -1) { + SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; + } - AFB_ApiInfo(mixer->api,"%s: create softvol", __func__); + AFB_ApiDebug(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); @@ -326,7 +377,7 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT goto OnErrorExit; } - AFB_ApiInfo(mixer->api,"%s: create softvol control", __func__); + AFB_ApiDebug(mixer->api,"%s: create softvol control", __func__); // create volume control before softvol pcm is opened volNumid = AlsaCtlCreateControl(mixer, @@ -345,19 +396,23 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT if ((zone->params->rate != stream->params->rate) || (zone->params->format != stream->params->format)) { AFB_ApiNotice(mixer->api, - "%s: Instanciate a RATE CONVERTER (stream [%d,%s(%d)], zone [%d,%s(%d)])", + "%s: Instanciate a RATE CONVERTER (stream [%d,%s(%d),%d channels], zone [%d,%s(%d), %d channels])", __func__, stream->params->rate, stream->params->formatS, stream->params->format, + stream->params->channels, zone->params->rate, zone->params->formatS, - zone->params->format); + zone->params->format, + zone->params->channels); char *rateName; - if (asprintf(&rateName, "rate-%s", stream->uid) == -1) + if (asprintf(&rateName, "rate-%s", stream->uid) == -1) { + SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; - streamPcm = AlsaCreateRate(mixer, rateName, streamPcm, zone->params, 0); + } + streamPcm = AlsaCreateRate(mixer, stream, rateName, streamPcm, zone->params, 0); if (!streamPcm) { AFB_ApiError(mixer->api, "%s: fail to create rate converter", __func__); goto OnErrorExit; @@ -368,7 +423,9 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT playbackName = (char*) streamPcm->cid.cardid; } - AFB_ApiInfo(mixer->api, "%s: Opening PCM PLAYBACK name %s", __func__, playbackName); + streamPcm->isPcmPlug = zone->isPcmPlug; + + AFB_ApiDebug(mixer->api, "%s: Opening PCM PLAYBACK name %s", __func__, playbackName); // 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*/ ); @@ -379,13 +436,15 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT 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 = AlsaPcmCopy(mixer, stream, capturePcm, streamPcm, stream->params); + error = AlsaPcmCopyStart(mixer, stream, capturePcm, streamPcm, stream->params); if (error) { AFB_ApiError(mixer->api, "%s: Failed to launch copy", __func__); goto OnErrorExit; } - + AFB_ApiDebug(mixer->api, "%s: register VOL ctrl", __func__); error = AlsaCtlRegister(mixer, captureCard, capturePcm, FONTEND_NUMID_IGNORE, volNumid); if (error) { AFB_ApiError(mixer->api, "%s: register control on capture", __func__); @@ -396,7 +455,8 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT if (loopDev && loopDev->numid) { // retrieve active/pause control and set PCM status accordingly error = AlsaCtlNumidGetLong(mixer, captureCard, loopDev->numid, &value); - if (error) goto OnErrorExit; + if (error) + goto OnErrorExit; // toggle pause/resume (should be done after pcm_start) if ((error = snd_pcm_pause(capturePcm->handle, !value)) < 0) { @@ -405,21 +465,33 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT } if (loop) { - if (asprintf((char**) &stream->source, "hw:%d,%d,%d", captureDev->cardidx, loop->playback, capturePcm->cid.subdev) == -1) - goto OnErrorExit; + if (asprintf((char**) &stream->source, "hw:%d,%d,%d", captureDev->cardidx, loop->playback, capturePcm->cid.subdev) == -1) { + SOFTMIXER_NOMEM(mixer->api); + goto OnErrorExit; + } + } else { - if (asprintf((char**) &stream->source, "hw:%d,%d,%d", captureDev->cardidx, captureDev->device, captureDev->subdev) == -1) + 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; @@ -437,7 +509,7 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT //AlsaDumpPcmInfo(source, "\n\nPcm_config\n-----------\n", streamPcm->handle); AFB_ApiNotice(mixer->api, - "%s: mixer=%s stream=%s done", + "%s: mixer=%s stream=%s CREATED", __func__, mixer->uid, stream->uid); return 0; @@ -450,34 +522,52 @@ OnErrorExit: } STATIC AlsaStreamAudioT * AttachOneStream(SoftMixerT *mixer, const char *uid, const char *prefix, json_object * streamJ) { - AlsaStreamAudioT *stream = calloc(1, sizeof (AlsaStreamAudioT)); 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,ss,s?s,s?i,s?b,s?o,s?s !}" + 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_ApiNotice(mixer->api, + AFB_ApiError(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_ApiError(mixer->api, "%s: A stream must have a zone or a playback", __func__); + goto OnErrorExit; + } + + if (stream->sink && stream->playback) { + AFB_ApiError(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_ApiError(mixer->api, @@ -488,22 +578,54 @@ STATIC AlsaStreamAudioT * AttachOneStream(SoftMixerT *mixer, const char *uid, co // make sure remain valid even when json object is removed stream->uid = strdup(stream->uid); - if (stream->sink)stream->sink = strdup(stream->sink); - if (stream->source)stream->source = strdup(stream->source); + 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) + 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) + if (asprintf((char**) &stream->verb, "%s#%s", prefix, stream->uid) == -1) { + SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; + } } } else { - if (!stream->verb) + 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 @@ -512,7 +634,6 @@ STATIC AlsaStreamAudioT * AttachOneStream(SoftMixerT *mixer, const char *uid, co AFB_ApiError(mixer->api, "%s: failed to create stream", __func__); goto OnErrorExit; } - return stream; OnErrorExit: @@ -521,55 +642,109 @@ OnErrorExit: return NULL; } -PUBLIC int ApiStreamAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, const char *prefix, json_object * argsJ) { +static void streamDestroy(SoftMixerT * mixer, void * arg) { + AlsaStreamAudioT * stream = (AlsaStreamAudioT*) arg; + int error = 0; + AFB_ApiDebug(mixer->api, "%s... %s", __func__, stream->uid); - AFB_ApiInfo(mixer->api, "%s: %s prefix %s", __func__, uid, prefix); - - if (!mixer->loops) { - AFB_ApiError(mixer->api, "%s: mixer=%s No Loop found [should Registry snd_loop first]", __func__, mixer->uid); - goto OnErrorExit; + error = afb_api_del_verb(mixer->api, stream->uid, (void**)stream->verbApiHandle); + if (error) { + AFB_ApiDebug(mixer->api, "%s: failed to remove verb %s", __func__, stream->uid); } - int index; - for (index = 0; index < mixer->max.streams; index++) { - if (!mixer->streams[index]) break; + AlsaPcmCopyStop(mixer, stream->copy); + + if (stream->softvolConfig) { + AFB_ApiDebug(mixer->api, "%s... %s delete softvol config", __func__, stream->uid); + snd_config_delete(stream->softvolConfig); + snd_config_update(); + stream->softvolConfig = NULL; + } + + if (stream->rateConfig) { + AFB_ApiDebug(mixer->api, "%s... %s delete rate config", __func__, stream->uid); + snd_config_delete(stream->rateConfig); + snd_config_update(); + stream->rateConfig = NULL; + } + + 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_ApiDebug(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_ReqT request, const char * uid, const char *prefix, json_object * argsJ) { + + AFB_ApiDebug(mixer->api, "%s: %s prefix %s", __func__, uid, prefix); + + AlsaStreamAudioT * newStream = NULL; + + if (mixer->nbLoops == 0) { + AFB_ApiError(mixer->api, "%s: mixer=%s No Loop found [should Registry snd_loop first]", __func__, mixer->uid); + goto fail; } - if (index == mixer->max.streams) { - AFB_ReqFailF(request, "too-small", "mixer=%s max stream=%d", mixer->uid, mixer->max.streams); - goto OnErrorExit; + if (mixer->nbStreams >= mixer->max.streams) { + AFB_ReqFailF(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: - mixer->streams[index] = AttachOneStream(mixer, uid, prefix, argsJ); - if (!mixer->streams[index]) { + newStream = streamCreate(mixer, uid, prefix, argsJ); + if (!newStream) { AFB_ReqFailF(request, "bad-stream", "mixer=%s invalid stream= %s", mixer->uid, json_object_get_string(argsJ)); - goto OnErrorExit; + goto fail; } + break; case json_type_array: count = json_object_array_length(argsJ); - if (count > (mixer->max.streams - index)) { + if (count > (mixer->max.streams - mixer->nbStreams)) { AFB_ReqFailF(request, "too-small", "mixer=%s max stream=%d", mixer->uid, mixer->max.streams); - goto OnErrorExit; + goto fail; } for (int idx = 0; idx < count; idx++) { json_object *streamJ = json_object_array_get_idx(argsJ, idx); - mixer->streams[index + idx] = AttachOneStream(mixer, uid, prefix, streamJ); - if (!mixer->streams[index + idx]) { + newStream = streamCreate(mixer, uid, prefix, streamJ); + if (!newStream) { AFB_ReqFailF(request, "bad-stream", "%s: mixer=%s invalid stream= %s", __func__, mixer->uid, json_object_get_string(streamJ)); - goto OnErrorExit; + goto fail; } } break; @@ -577,12 +752,12 @@ PUBLIC int ApiStreamAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, AFB_ReqFailF(request, "invalid-syntax", "mixer=%s streams invalid argsJ= %s", mixer->uid, json_object_get_string(argsJ)); - goto OnErrorExit; + goto fail; } return 0; -OnErrorExit: +fail: AFB_ApiError(mixer->api, "%s FAILED\n", __func__); return -1; } |