/* * 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 // move from vol % to absolute value #define CONVERT_RANGE(val, min, max) ceil((val) * ((max) - (min)) * 0.01 + (min)) #define CONVERT_VOLUME(val, min, max) (int) CONVERT_RANGE ((double)val, (double)min, (double)max) // move from volume to percentage (extract from alsa-utils) STATIC int CONVERT_PERCENT(long val, long min, long max) { long range = max - min; int tmp; if (range == 0) return 0; val -= min; tmp = (int) rint((double) val / (double) range * 100); return tmp; } typedef enum { RVOL_ABS, RVOL_ADD, RVOL_DEL, RVOL_NONE } volumeT; typedef struct { const char *uid; SoftMixerT *mixer; AlsaSndPcmT* pcm; } apiVerbHandleT; STATIC AlsaPcmChannelT * ProcessOneChannel(SoftMixerT *mixer, const char *uid, json_object *argsJ) { AlsaPcmChannelT *channel = calloc(1, sizeof (AlsaPcmChannelT)); if (channel == NULL) { SOFTMIXER_NOMEM(mixer->api); goto fail; } int error = wrap_json_unpack(argsJ, "{ss,si !}", "uid", &channel->uid, "port", &channel->port); if (error) { AFB_ApiError(mixer->api, "%s: sndcard=%s channel: missing (uid||port) error=%s json=%s", __func__, uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); goto fail_channel; } CDS_INIT_LIST_HEAD(&channel->list); channel->uid = strdup(channel->uid); if (channel->uid == NULL) { SOFTMIXER_NOMEM(mixer->api); goto fail_channel; } return channel; fail_channel: free(channel); fail: return NULL; } STATIC int PcmAttachOneCtl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, json_object *argsJ, AlsaSndControlT *control) { snd_ctl_elem_id_t* elemId = NULL; snd_ctl_elem_info_t *elemInfo; int numid = 0; long value = ALSA_DEFAULT_PCM_VOLUME; const char *name; int error = wrap_json_unpack(argsJ, "{s?i,s?s,s?i !}" , "numid", &numid , "name", &name , "value", &value ); if (error || (!numid && !name)) { AFB_ApiError(mixer->api, "%s: cardid=%s channel: missing (numid|name|value) error=%s json=%s", __func__, sndcard->cid.name, wrap_json_get_error_string(error), json_object_get_string(argsJ)); goto fail; } if (numid > 0) { elemId = AlsaCtlGetNumidElemId(mixer, sndcard, numid); if (!elemId) { AFB_ApiError(mixer->api, "%s sndard=%s fail to find control numid=%d", __func__, sndcard->cid.cardid, numid); goto fail; } } else { elemId = AlsaCtlGetNameElemId(mixer, sndcard, name); if (!elemId) { AFB_ApiError(mixer->api, "%s sndard=%s fail to find control name=%s", __func__, sndcard->cid.cardid, name); goto fail; } } snd_ctl_elem_info_alloca(&elemInfo); snd_ctl_elem_info_set_id(elemInfo, elemId); control->name = strdup(snd_ctl_elem_info_get_name(elemInfo)); if (!control->name) { SOFTMIXER_NOMEM(mixer->api); goto fail_elemId; } control->numid = snd_ctl_elem_info_get_numid(elemInfo); if (snd_ctl_elem_info(sndcard->ctl, elemInfo) < 0) { AFB_ApiError(mixer->api, "%s: sndard=%s numid=%d name='%s' not loadable", __func__, sndcard->cid.cardid, control->numid, control->name); goto fail_control_name; } if (!snd_ctl_elem_info_is_writable(elemInfo)) { AFB_ApiError(mixer->api, "%s: sndard=%s numid=%d name='%s' not writable", __func__, sndcard->cid.cardid, control->numid, control->name); goto fail_control_name; } control->count = snd_ctl_elem_info_get_count(elemInfo); switch (snd_ctl_elem_info_get_type(elemInfo)) { case SND_CTL_ELEM_TYPE_BOOLEAN: control->min = 0; control->max = 1; control->step = 0; error = CtlElemIdSetLong(mixer, sndcard, elemId, value); break; case SND_CTL_ELEM_TYPE_INTEGER: case SND_CTL_ELEM_TYPE_INTEGER64: control->min = snd_ctl_elem_info_get_min(elemInfo); control->max = snd_ctl_elem_info_get_max(elemInfo); control->step = snd_ctl_elem_info_get_step(elemInfo); error = CtlElemIdSetLong(mixer, sndcard, elemId, (int) CONVERT_VOLUME(value, control->min, control->max)); break; default: AFB_ApiError(mixer->api, "%s: sndard=%s numid=%d name='%s' invalid/unsupported type=%d", __func__, sndcard->cid.cardid, control->numid, control->name, snd_ctl_elem_info_get_type(elemInfo)); goto fail_control_name; } if (error) { AFB_ApiError(mixer->api, "%s: sndard=%s numid=%d name='%s' not writable", __func__, sndcard->cid.cardid, control->numid, control->name); goto fail_control_name; } free(elemId); return 0; fail_control_name: free((char*)control->name); fail_elemId: free(elemId); fail: return -1; } STATIC int PcmSetControl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaSndControlT *control, volumeT volType, int *newvol, int *oldval) { snd_ctl_elem_id_t* elemId = NULL; snd_ctl_elem_info_t *elemInfo; int error, value = 0; long curval; assert(control->numid); elemId = AlsaCtlGetNumidElemId(mixer, sndcard, control->numid); if (!elemId) { AFB_ApiError(mixer->api, "%s sndard=%s fail to find control numid=%d", __func__, sndcard->cid.cardid, control->numid); goto OnErrorExit; } snd_ctl_elem_info_alloca(&elemInfo); snd_ctl_elem_info_set_id(elemInfo, elemId); if (snd_ctl_elem_info(sndcard->ctl, elemInfo) < 0) { AFB_ApiError(mixer->api, "%s: sndard=%s numid=%d name='%s' not loadable", __func__, sndcard->cid.cardid, control->numid, control->name); goto OnErrorExit; } if (!snd_ctl_elem_info_is_writable(elemInfo)) { AFB_ApiError(mixer->api, "%s: sndard=%s numid=%d name='%s' not writable", __func__, sndcard->cid.cardid, control->numid, control->name); goto OnErrorExit; } error = CtlElemIdGetLong(mixer, sndcard, elemId, &curval); if (error) { AFB_ApiError(mixer->api, "%s sndcard=%s fail to read control numid=%d", __func__, sndcard->cid.cardid, control->numid); goto OnErrorExit; } switch (snd_ctl_elem_info_get_type(elemInfo)) { case SND_CTL_ELEM_TYPE_BOOLEAN: error = CtlElemIdSetLong(mixer, sndcard, elemId, *newvol); break; case SND_CTL_ELEM_TYPE_INTEGER: case SND_CTL_ELEM_TYPE_INTEGER64: switch (volType) { case RVOL_ADD: value = CONVERT_PERCENT(curval, control->min, control->max) + *newvol; break; case RVOL_DEL: value = CONVERT_PERCENT(curval, control->min, control->max) - *newvol; break; default: value = *newvol; } error = CtlElemIdSetLong(mixer, sndcard, elemId, CONVERT_VOLUME(value, control->min, control->max)); if (error) { AFB_ApiError(mixer->api, "PcmSetControl sndard=%s fail to write control numid=%d value=%d", sndcard->cid.cardid, control->numid, value); goto OnErrorExit; } break; default: AFB_ApiError(mixer->api, "PcmSetControl: sndard=%s numid=%d name='%s' invalid/unsupported type=%d", sndcard->cid.cardid, control->numid, control->name, snd_ctl_elem_info_get_type(elemInfo)); goto OnErrorExit; } if (error) { AFB_ApiError(mixer->api, "PcmSetControl: sndard=%s numid=%d name='%s' not writable", sndcard->cid.cardid, control->numid, control->name); goto OnErrorExit; } *oldval = CONVERT_PERCENT(curval, control->min, control->max); *newvol = value; free(elemId); return 0; OnErrorExit: if (elemId)free(elemId); return -1; } STATIC void ApiPcmVerbCB(AFB_ReqT request) { apiVerbHandleT *handle = (apiVerbHandleT*) afb_req_get_vcbdata(request); int error, verbose = 0, doInfo = 0, doToggle = 0, doMute = -1; json_object *volumeJ = NULL; json_object *responseJ = NULL; json_object *argsJ = afb_req_json(request); SoftMixerT *mixer = handle->mixer; AlsaSndCtlT *sndcard = handle->pcm->sndcard; assert(mixer && sndcard); error = wrap_json_unpack(argsJ, "{s?b,s?b,s?b,s?b,s?o !}" , "verbose", &verbose , "info", &doInfo , "mute", &doMute , "toggle", &doToggle , "volume", &volumeJ ); if (error) { AFB_ReqFailF(request, "syntax-error", "Missing 'mute|volume|toggle|quiet' args=%s error=%s", json_object_get_string(argsJ), wrap_json_get_error_string(error)); goto OnErrorExit; } if (verbose) responseJ=json_object_new_object(); if (doMute != -1) { int mute = (int) doMute; error += AlsaCtlNumidSetLong(mixer, sndcard, handle->pcm->mute.numid, mute); if (error) { AFB_ReqFailF(request, "invalid-numid", "Fail to set pause numid=%d", handle->pcm->mute.numid); goto OnErrorExit; } if (verbose) { json_object_object_add(responseJ, "mute", json_object_new_boolean((json_bool) mute)); } } if (doToggle) { long mute; error += AlsaCtlNumidGetLong(mixer, handle->pcm->sndcard, handle->pcm->mute.numid, &mute); error += AlsaCtlNumidSetLong(mixer, handle->pcm->sndcard, handle->pcm->mute.numid, !mute); if (error) { AFB_ReqFailF(request, "invalid-numid", "Fail to toogle pause numid=%d", handle->pcm->mute.numid); goto OnErrorExit; } if (verbose) { json_object_object_add(responseJ, "mute", json_object_new_boolean((json_bool)!mute)); } } if (volumeJ) { volumeT volType; int newvol, oldvol; const char*volString; switch (json_object_get_type(volumeJ)) { case json_type_string: volString = json_object_get_string(volumeJ); switch (volString[0]) { case '+': sscanf(&volString[1], "%d", &newvol); volType = RVOL_ADD; break; case '-': sscanf(&volString[1], "%d", &newvol); volType = RVOL_DEL; break; default: error = sscanf(&volString[0], "%d", &newvol); volType = RVOL_ABS; if (error != 1) { AFB_ReqFailF(request, "not-integer", "relative volume should start by '+|-' value=%s", json_object_get_string(volumeJ)); goto OnErrorExit; } } break; case json_type_int: volType = RVOL_ABS; newvol = json_object_get_int(volumeJ); break; case json_type_null: volType=RVOL_ADD; newvol=0; break; default: AFB_ReqFailF(request, "not-integer", "volume should be string or integer value=%s", json_object_get_string(volumeJ)); goto OnErrorExit; } error = PcmSetControl(mixer, handle->pcm->sndcard, &handle->pcm->volume, volType, &newvol, &oldvol); if (error) { AFB_ReqFailF(request, "invalid-ctl", "Fail to set volume hal=%s card=%s numid=%d name=%s value=%d" , handle->uid, handle->pcm->sndcard->cid.cardid, handle->pcm->volume.numid, handle->pcm->volume.name, newvol); goto OnErrorExit; } if (verbose) { json_object_object_add(responseJ, "volnew", json_object_new_int(newvol)); json_object_object_add(responseJ, "volold", json_object_new_int(oldvol)); } } AFB_ReqSuccess(request, responseJ, handle->uid); return; OnErrorExit: return; } PUBLIC void ApiPcmDelParams(SoftMixerT* mixer, AlsaPcmHwInfoT* params) { AFB_ApiDebug(mixer->api, "%s... free params formatS %s", __func__, params->formatS); free((char*)params->formatS); free(params); } PUBLIC AlsaPcmHwInfoT * ApiPcmSetParams(SoftMixerT *mixer, const char *uid, json_object * paramsJ) { const char *format = NULL, *access = NULL; AlsaPcmHwInfoT *params = calloc(1, sizeof (AlsaPcmHwInfoT)); if (params == NULL) { SOFTMIXER_NOMEM(mixer->api); goto fail; } // some default values params->rate = ALSA_DEFAULT_PCM_RATE; params->channels = 2; params->sampleSize = 0; if (paramsJ) { int error = wrap_json_unpack(paramsJ, "{s?i,s?i, s?s, s?s !}", "rate", ¶ms->rate, "channels",¶ms->channels, "format", &format, "access", &access); if (error) { AFB_ApiError(mixer->api, "%s: sndcard=%s invalid params=%s", __func__, uid, json_object_get_string(paramsJ)); goto fail_params; } } if (!format) { params->format = SND_PCM_FORMAT_S16_LE; params->formatS = strdup("S16_LE"); if (params->formatS == NULL) { SOFTMIXER_NOMEM(mixer->api); goto fail_params; } goto check_access; } params->formatS = strdup(format); if (params->formatS == NULL) { SOFTMIXER_NOMEM(mixer->api); goto fail_params; } #define FORMAT_CHECK(arg) if (!strcmp(format,#arg)) { params->format = SND_PCM_FORMAT_##arg; goto check_access; } FORMAT_CHECK(S16_LE); FORMAT_CHECK(S16_BE); FORMAT_CHECK(U16_LE); FORMAT_CHECK(U16_BE); FORMAT_CHECK(S32_BE); FORMAT_CHECK(S32_LE); FORMAT_CHECK(U32_BE); FORMAT_CHECK(U32_LE); FORMAT_CHECK(S24_BE); FORMAT_CHECK(S24_LE); FORMAT_CHECK(U24_BE); FORMAT_CHECK(U24_LE); FORMAT_CHECK(S8); FORMAT_CHECK(U8); FORMAT_CHECK(FLOAT_LE); FORMAT_CHECK(FLOAT_BE); AFB_ApiError(mixer->api, "%s: %s(params) unsupported format 'S16_LE|S32_L|...' format=%s", __func__, uid, format); goto fail_params; check_access: AFB_ApiNotice(mixer->api, "%s: %s format set to SND_PCM_FORMAT_%s", __func__, uid, params->formatS); #define ACCESS_CHECK(arg) if (!strcmp(access,#arg)) { params->access = SND_PCM_ACCESS_##arg; goto success;} if (!access) { params->access = SND_PCM_ACCESS_RW_INTERLEAVED; goto success; } ACCESS_CHECK(MMAP_INTERLEAVED); ACCESS_CHECK(MMAP_NONINTERLEAVED); ACCESS_CHECK(MMAP_COMPLEX); ACCESS_CHECK(RW_INTERLEAVED); ACCESS_CHECK(RW_NONINTERLEAVED); AFB_ApiNotice(mixer->api, "%s:%s(params) unsupported access 'RW_INTERLEAVED|MMAP_INTERLEAVED|MMAP_COMPLEX' access=%s", __func__,uid, access); goto fail_params; success: AFB_ApiNotice(mixer->api, "%s:%s access set to %s", __func__, uid, access); return params; fail_params: free(params); fail: return NULL; } static void pcmChannelsDestroy(SoftMixerT * mixer, AlsaSndPcmT * pcm) { AFB_ApiDebug(mixer->api, "%s: pcm %s", __func__, pcm->uid); AlsaPcmChannelT * channel, *tmp; cds_list_for_each_entry_safe(channel, tmp, &pcm->channels.list, list) { cds_list_del(&channel->list); free((char *)channel->uid); free(channel); } AFB_ApiDebug(mixer->api, "%s: pcm %s DONE", __func__, pcm->uid); } PUBLIC void ApiPcmDelete(SoftMixerT * mixer, AlsaSndPcmT * pcm) { AFB_ApiDebug(mixer->api, "%s: pcm %s", __func__, pcm->uid); free(pcm->apiVerbHandle); AlsaSndCtlT * card = pcm->sndcard; if (card->cid.pcmplug_params) free((char*)card->cid.pcmplug_params); pcmChannelsDestroy(mixer, pcm); snd_ctl_close(card->ctl); free(card); free ((char*)pcm->mute.name); free ((char*)pcm->volume.name); cds_list_del(&pcm->list); if (pcm->verb && (strcmp(pcm->verb, SOFTMIXER_VERB_NONE)!=0)) { int error = afb_api_del_verb(mixer->api, pcm->verb, (void**)&pcm->apiVerbHandle); if (error) { AFB_ApiError(mixer->api, "Failed to remove verb %s", pcm->verb); } } free((char*)pcm->uid); free(pcm); AFB_ApiDebug(mixer->api, "%s: done", __func__); } PUBLIC AlsaSndPcmT * ApiPcmNew(SoftMixerT* mixer) { AlsaSndPcmT *pcm = calloc(1, sizeof (AlsaSndPcmT)); if (pcm == NULL) { SOFTMIXER_NOMEM(mixer->api); goto fail; } CDS_INIT_LIST_HEAD(&pcm->list); CDS_INIT_LIST_HEAD(&pcm->channels.list); fail: return pcm; } PUBLIC AlsaSndPcmT * ApiPcmAttachOne(SoftMixerT *mixer, const char *uid, snd_pcm_stream_t direction, json_object * argsJ) { json_object *sourceJ = NULL, *paramsJ = NULL, *sinkJ = NULL, *targetJ = NULL; char *apiVerb = NULL, *apiInfo = NULL; apiVerbHandleT *handle = NULL; int error; AlsaSndPcmT *pcm = ApiPcmNew(mixer); if (pcm == NULL) { SOFTMIXER_NOMEM(mixer->api); goto fail; } pcm->sndcard = (AlsaSndCtlT*) calloc(1, sizeof (AlsaSndCtlT)); if (pcm->sndcard == NULL) { SOFTMIXER_NOMEM(mixer->api); goto fail_pcm; } CDS_INIT_LIST_HEAD(&pcm->sndcard->registryList); error = wrap_json_unpack(argsJ, "{ss,s?s,s?s,s?s,s?i,s?i,s?o,s?o,s?o !}" , "uid", &pcm->uid , "pcmplug_params", &pcm->sndcard->cid.pcmplug_params , "path", &pcm->sndcard->cid.devpath , "cardid", &pcm->sndcard->cid.cardid , "device", &pcm->sndcard->cid.device , "subdev", &pcm->sndcard->cid.subdev , "sink", &sinkJ , "source", &sourceJ , "params", ¶msJ ); if (error) { AFB_ApiError(mixer->api, "%s: hal=%s missing 'uid|path|cardid|device|sink|source|params' error=%s args=%s", __func__, uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); goto fail_pcm_sndcard; } if (pcm->sndcard->cid.pcmplug_params) pcm->isPcmPlug = true; else pcm->isPcmPlug = false; // try to open sound card control interface pcm->sndcard->ctl = AlsaByPathOpenCtl(mixer, pcm->uid, pcm->sndcard); if (!pcm->sndcard->ctl) { AFB_ApiError(mixer->api, "%s: hal=%s Fail to open sndcard uid=%s devpath=%s cardid=%s", __func__, uid, pcm->uid, pcm->sndcard->cid.devpath, pcm->sndcard->cid.cardid); goto fail_pcm_sndcard; } // check sndcard accepts params pcm->sndcard->params = ApiPcmSetParams(mixer, pcm->uid, paramsJ); if (!pcm->sndcard->params) { AFB_ApiError(mixer->api, "%s: hal=%s Fail to set params sndcard uid=%s params=%s", __func__, uid, pcm->uid, json_object_get_string(paramsJ)); goto fail_pcm_sndcard_ctl; } if (direction == SND_PCM_STREAM_PLAYBACK) { if (!sinkJ) { AFB_ApiError(mixer->api, "%s: hal=%s SND_PCM_STREAM_PLAYBACK require sinks args=%s", __func__, uid, json_object_get_string(argsJ)); goto fail_pcm_sndcard_ctl; } targetJ = sinkJ; } if (direction == SND_PCM_STREAM_CAPTURE) { if (!sourceJ) { AFB_ApiError(mixer->api, "%s: hal=%s SND_PCM_STREAM_CAPTURE require sources args=%s", __func__, uid, json_object_get_string(argsJ)); goto fail_pcm_sndcard_ctl; } targetJ = sourceJ; } json_object *channelsJ = NULL, *controlsJ = NULL; error = wrap_json_unpack(targetJ, "{so,s?o !}" , "channels", &channelsJ , "controls", &controlsJ ); if (error) { AFB_ApiNotice(mixer->api, "%s: hal=%s pcms missing channels|[controls] error=%s (%s)", __func__, uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); goto fail_pcm_sndcard_ctl; } AlsaPcmChannelT * channel = NULL; if (channelsJ) { switch (json_object_get_type(channelsJ)) { case json_type_object: channel = ProcessOneChannel(mixer, pcm->uid, channelsJ); if (channel == NULL) { goto fail_pcm_sndcard_ctl; } pcm->nbChannels++; cds_list_add_tail(&channel->list, &pcm->channels.list); break; case json_type_array: { int nbChannels = (int) json_object_array_length(channelsJ); for (int idx = 0; idx < nbChannels; idx++) { json_object *channelJ = json_object_array_get_idx(channelsJ, idx); channel = ProcessOneChannel(mixer, pcm->uid, channelJ); if (!channel) goto fail_pcm_channels; pcm->nbChannels++; cds_list_add_tail(&channel->list, &pcm->channels.list); } break; } default: AFB_ApiError(mixer->api, "%s:%s invalid pcm=%s", pcm->uid, __func__, json_object_get_string(channelsJ)); goto fail_pcm_sndcard_ctl; } } if (controlsJ) { json_object *volJ = NULL, *muteJ = NULL; error = wrap_json_unpack(controlsJ, "{s?o,s?o !}" , "volume", &volJ , "mute", &muteJ ); if (error) { AFB_ApiNotice(mixer->api, "%s: source missing [volume]|[mute] error=%s control=%s", __func__, wrap_json_get_error_string(error), json_object_get_string(controlsJ)); goto fail_pcm_channels; } if (volJ) { error = PcmAttachOneCtl(mixer, pcm->sndcard, volJ, &pcm->volume); if (error) goto fail_pcm_channels; } if (muteJ) { error = PcmAttachOneCtl(mixer, pcm->sndcard, muteJ, &pcm->mute); if (error) goto fail_pcm_channels; } // create master control for this sink if (direction == SND_PCM_STREAM_PLAYBACK) { if (asprintf(&apiVerb, "%s:playback", pcm->uid) == -1) { SOFTMIXER_NOMEM(mixer->api); goto fail_pcm_channels; } if (asprintf(&apiInfo, "HAL:%s SND_PCM_STREAM_PLAYBACK", uid) == -1) { SOFTMIXER_NOMEM(mixer->api); goto fail_verb_name; } } else { if (asprintf(&apiVerb, "%s:capture", pcm->uid) == -1) { SOFTMIXER_NOMEM(mixer->api); goto fail_pcm_channels; } if (asprintf(&apiInfo, "HAL:%s SND_PCM_STREAM_PLAYBACK", uid) == -1) { SOFTMIXER_NOMEM(mixer->api); goto fail_verb_name; } } apiVerbHandleT *handle = calloc(1, sizeof (apiVerbHandleT)); if (handle == NULL) { SOFTMIXER_NOMEM(mixer->api); goto fail_api_info; } handle->uid = uid; handle->pcm = pcm; handle->mixer = mixer; pcm->verb=apiVerb; pcm->apiVerbHandle = handle; error = afb_api_add_verb(mixer->api, apiVerb, apiInfo, ApiPcmVerbCB, handle, NULL, 0, 0); if (error) { AFB_ApiError(mixer->api, "%s mixer=%s verb=%s fail to Register Master control ", __func__, mixer->uid, apiVerb); goto fail_handle; } } else { /* no controls -> put dummy verb */ pcm->verb = SOFTMIXER_VERB_NONE; } // free useless resource and secure others pcm->uid = strdup(pcm->uid); if (pcm->uid == NULL) { SOFTMIXER_NOMEM(mixer->api); goto fail_handle; } const char ** ppcmplug = &(pcm->sndcard->cid.pcmplug_params); if (*ppcmplug) { *ppcmplug = strdup(*ppcmplug); if (*ppcmplug == NULL) { SOFTMIXER_NOMEM(mixer->api); goto fail_pcm_uid; } } return pcm; fail_pcm_uid: free((char*)pcm->uid); fail_handle: free(handle); fail_api_info: free(apiInfo); fail_verb_name: free(apiVerb); fail_pcm_channels: pcmChannelsDestroy(mixer, pcm); fail_pcm_sndcard_ctl: free(pcm->sndcard->ctl); fail_pcm_sndcard: free(pcm->sndcard); fail_pcm: free ((char*)pcm->mute.name); free ((char*)pcm->volume.name); free(pcm); fail: return NULL; }