From e0f57e523112e1bc73a04e8615d7a21355f0ce0e Mon Sep 17 00:00:00 2001 From: Thierry Bultel Date: Tue, 4 Dec 2018 23:11:20 +0100 Subject: Add support for bluetooth telephony This adds support for bluetooth telephony. A big rework in the softmixer internals has been mandatory, in order to support dynamic streams creation and deletions. Bluetooth telephony relies on the recent evolutions of bluez-alsa, the most important one being the support of HFP over Ofono. The softmixer opens PCM ioplugs provided by bluez-alsa. Bluetooth SCO needs 2 streams, one for listening and the other for talking. These streams are created upon requests sent by the hal-manager. The hal manager subscribes for bluez-alsa events and request the list of availalble transports. For each "attach" transaction verb, the softmixer maintains a list of the all created objects (sources, sinks, zones, ramps, streams, and more) Additionnally, it creates a new verb when the attach succeeds, that verb is typically something like "sco_XX:XX:XX:XX:XX:XX", and the only supported action at the present time is {"action":"remove"}, that performs all the cleanup of the registered objects. Change-Id: I1b119e6c079e60daf771e63c083a1ef33a39f379 Signed-off-by: Thierry Bultel --- plugins/alsa/alsa-api-loop.c | 247 +++++++++++++++++++------- plugins/alsa/alsa-api-mixer.c | 342 ++++++++++++++++++----------------- plugins/alsa/alsa-api-pcm.c | 375 +++++++++++++++++++++++++++++---------- plugins/alsa/alsa-api-ramp.c | 86 +++++---- plugins/alsa/alsa-api-sink.c | 153 +++++++++------- plugins/alsa/alsa-api-source.c | 74 +++++--- plugins/alsa/alsa-api-streams.c | 325 +++++++++++++++++++++++++-------- plugins/alsa/alsa-api-zones.c | 226 ++++++++++++++++------- plugins/alsa/alsa-bluez.c | 71 -------- plugins/alsa/alsa-bluez.h | 29 --- plugins/alsa/alsa-core-ctl.c | 196 ++++++++++++-------- plugins/alsa/alsa-core-pcm.c | 307 ++++++++++++++++++++------------ plugins/alsa/alsa-effect-ramp.c | 36 ++-- plugins/alsa/alsa-plug-dmix.c | 16 +- plugins/alsa/alsa-plug-rate.c | 26 ++- plugins/alsa/alsa-plug-route.c | 214 +++++++++++++++------- plugins/alsa/alsa-plug-vol.c | 23 ++- plugins/alsa/alsa-ringbuf.c | 6 + plugins/alsa/alsa-softmixer.h | 148 +++++++++++---- plugins/alsa/alsa-transaction.c | 111 ++++++++++++ plugins/alsa/alsa-transaction.h | 37 ++++ plugins/alsa/alsa-utils-bypath.c | 155 ++++++++++++---- plugins/alsa/alsa-utils-dump.c | 17 +- plugins/alsa/ringbuf.c | 2 +- 24 files changed, 2166 insertions(+), 1056 deletions(-) delete mode 100644 plugins/alsa/alsa-bluez.c delete mode 100644 plugins/alsa/alsa-bluez.h create mode 100644 plugins/alsa/alsa-transaction.c create mode 100644 plugins/alsa/alsa-transaction.h diff --git a/plugins/alsa/alsa-api-loop.c b/plugins/alsa/alsa-api-loop.c index 61bdc99..16491a0 100644 --- a/plugins/alsa/alsa-api-loop.c +++ b/plugins/alsa/alsa-api-loop.c @@ -24,31 +24,42 @@ PUBLIC AlsaLoopSubdevT *ApiLoopFindSubdev(SoftMixerT *mixer, const char *streamUid, const char *targetUid, AlsaSndLoopT **loop) { // Either allocate a free loop subdev or search for a specific targetUid when specified - if (targetUid) { - for (int idx = 0; mixer->loops[idx]; idx++) { - for (int jdx = 0; jdx < mixer->loops[idx]->scount; jdx++) { - if (mixer->loops[idx]->subdevs[jdx]->uid && !strcasecmp(mixer->loops[idx]->subdevs[jdx]->uid, targetUid)) { - *loop = mixer->loops[idx]; - return mixer->loops[idx]->subdevs[jdx]; - } - } - } - } else { - for (int idx = 0; mixer->loops[idx]; idx++) { - for (int jdx = 0; mixer->loops[idx]->subdevs[jdx]; jdx++) { - if (!mixer->loops[idx]->subdevs[jdx]->uid) { - mixer->loops[idx]->subdevs[jdx]->uid = streamUid; - *loop = mixer->loops[idx]; - return mixer->loops[idx]->subdevs[jdx]; - } - } - } - } - return NULL; + AlsaSndLoopT * _loop; + if (targetUid) { + cds_list_for_each_entry(_loop, &mixer->loops.list, list) { + AlsaLoopSubdevT * subdev; + cds_list_for_each_entry(subdev, &_loop->subdevs.list, list) { + if (subdev->uid && !strcmp(subdev->uid, targetUid)) { + *loop = _loop; + return subdev; + } + } + } + } else { + cds_list_for_each_entry(_loop, &mixer->loops.list, list) { + AlsaLoopSubdevT * subdev; + cds_list_for_each_entry(subdev, &_loop->subdevs.list, list) { + if (!subdev->uid) { + subdev->uid = streamUid; + *loop = _loop; + return subdev; + } + } + } + } + return NULL; } + STATIC AlsaLoopSubdevT *ProcessOneSubdev(SoftMixerT *mixer, AlsaSndLoopT *loop, json_object *subdevJ) { - AlsaLoopSubdevT *subdev = calloc(1, sizeof (AlsaPcmCtlT)); + + AFB_ApiDebug(mixer->api, "%s", __func__); + + AlsaLoopSubdevT *subdev = calloc(1, sizeof (AlsaLoopSubdevT)); + if (subdev == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } int error = wrap_json_unpack(subdevJ, "{s?s, si,si !}" , "uid", &subdev->uid @@ -56,12 +67,20 @@ STATIC AlsaLoopSubdevT *ProcessOneSubdev(SoftMixerT *mixer, AlsaSndLoopT *loop, , "numid", &subdev->numid ); if (error) { - AFB_ApiError(mixer->api, "ProcessOneSubdev: loop=%s missing (uid|subdev|numid) error=%s json=%s", loop->uid, wrap_json_get_error_string(error),json_object_get_string(subdevJ)); - goto OnErrorExit; + AFB_ApiError(mixer->api, + "%s: loop=%s missing (uid|subdev|numid) error=%s json=%s", + __func__, loop->uid, wrap_json_get_error_string(error),json_object_get_string(subdevJ)); + goto fail_subdev; } // subdev with no UID are dynamically attached - if (subdev->uid) subdev->uid = strdup(subdev->uid); + if (subdev->uid) { + subdev->uid = strdup(subdev->uid); + if (subdev->uid == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto fail_subdev; + } + } // create loop subdev entry point with cardidx+device+subdev in order to open subdev and not sndcard AlsaDevInfoT loopSubdev; @@ -73,25 +92,61 @@ STATIC AlsaLoopSubdevT *ProcessOneSubdev(SoftMixerT *mixer, AlsaSndLoopT *loop, loopSubdev.subdev = subdev->index; // assert we may open this loopback subdev in capture mode - AlsaPcmCtlT *pcmInfo = AlsaByPathOpenPcm(mixer, &loopSubdev, SND_PCM_STREAM_CAPTURE); - if (!pcmInfo) goto OnErrorExit; + AlsaPcmCtlT *pcmCtl = AlsaByPathOpenPcmCtl(mixer, &loopSubdev, SND_PCM_STREAM_CAPTURE); + if (!pcmCtl) { + goto fail_subdev_uid; + } + + pcmCtl->closeAtDeletion = true; // free PCM as we only open loop to assert it's a valid capture device - snd_pcm_close(pcmInfo->handle); - free(pcmInfo); + AlsaPcmCtlDelete(mixer, pcmCtl); + + AFB_ApiDebug(mixer->api, "%s DONE", __func__); return subdev; -OnErrorExit: +fail_subdev_uid: + free((char*)subdev->uid); +fail_subdev: + free(subdev); +fail: return NULL; } +static void freeSubdev(AlsaLoopSubdevT * subdev) { + if (subdev->uid) + free((char*)subdev->uid); + free(subdev); +} + + STATIC AlsaSndLoopT *AttachOneLoop(SoftMixerT *mixer, const char *uid, json_object *argsJ) { - AlsaSndLoopT *loop = calloc(1, sizeof (AlsaSndLoopT)); + json_object *subdevsJ = NULL, *devicesJ = NULL; int error; - loop->sndcard = (AlsaSndCtlT*) calloc(1, sizeof (AlsaSndCtlT)); + AFB_ApiNotice(mixer->api, "%s, uid %s", __func__, uid); + + AlsaSndLoopT *loop = calloc(1, sizeof (AlsaSndLoopT)); + if (loop == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } + + CDS_INIT_LIST_HEAD(&loop->list); + CDS_INIT_LIST_HEAD(&loop->subdevs.list); + + AlsaSndCtlT * sndctl = (AlsaSndCtlT*) calloc(1, sizeof (AlsaSndCtlT)); + if (sndctl == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto fail_loop; + } + + CDS_INIT_LIST_HEAD(&sndctl->registryList); + + loop->sndcard = sndctl; + error = wrap_json_unpack(argsJ, "{ss,s?s,s?s,so,so !}" , "uid", &loop->uid , "path", &loop->sndcard->cid.devpath @@ -102,7 +157,7 @@ STATIC AlsaSndLoopT *AttachOneLoop(SoftMixerT *mixer, const char *uid, json_obje if (error || !loop->uid || !subdevsJ || (!loop->sndcard->cid.devpath && !loop->sndcard->cid.cardid)) { AFB_ApiNotice(mixer->api, "%s mixer=%s hal=%s missing 'uid|path|cardid|devices|subdevs' error=%s args=%s", __func__, mixer->uid, uid, wrap_json_get_error_string(error),json_object_get_string(argsJ)); - goto OnErrorExit; + goto fail_snd_card; } // try to open sound card control interface @@ -110,10 +165,10 @@ STATIC AlsaSndLoopT *AttachOneLoop(SoftMixerT *mixer, const char *uid, json_obje if (!loop->sndcard->ctl) { AFB_ApiError(mixer->api, "%s mixer=%s hal=%s Fail open sndcard loop=%s devpath=%s cardid=%s (please check 'modprobe snd_aloop')", __func__, mixer->uid, uid, loop->uid, loop->sndcard->cid.devpath, loop->sndcard->cid.cardid); - goto OnErrorExit; + goto fail_snd_card; } - // Default devices is payback=0 capture=1 + // Default devices is playback=0 capture=1 if (!devicesJ) { loop->playback = 0; loop->capture = 1; @@ -122,87 +177,141 @@ STATIC AlsaSndLoopT *AttachOneLoop(SoftMixerT *mixer, const char *uid, json_obje if (error) { AFB_ApiNotice(mixer->api, "%s mixer=%s hal=%s Loop=%s missing 'capture|playback' error=%s devices=%s", __func__, mixer->uid, uid, loop->uid, wrap_json_get_error_string(error),json_object_get_string(devicesJ)); - goto OnErrorExit; + goto fail_snd_card_ctl; } } + AlsaLoopSubdevT * subdev; + switch (json_object_get_type(subdevsJ)) { case json_type_object: - loop->scount = 1; - loop->subdevs = calloc(2, sizeof (void*)); - loop->subdevs[0] = ProcessOneSubdev(mixer, loop, subdevsJ); - if (!loop->subdevs[0]) goto OnErrorExit; + subdev = ProcessOneSubdev(mixer, loop, subdevsJ); + if (!subdev) + goto fail_loop_subdev; + loop->nbSubdevs++; + cds_list_add_tail(&subdev->list, &loop->subdevs.list); break; - case json_type_array: - loop->scount = (int) json_object_array_length(subdevsJ); - loop->subdevs = calloc(loop->scount + 1, sizeof (void*)); - for (int idx = 0; idx < loop->scount; idx++) { + case json_type_array: { + int nbSubDevs = (int) json_object_array_length(subdevsJ); + for (int idx = 0; idx < nbSubDevs; idx++) { json_object *subdevJ = json_object_array_get_idx(subdevsJ, idx); - loop->subdevs[idx] = ProcessOneSubdev(mixer, loop, subdevJ); - if (!loop->subdevs[idx]) goto OnErrorExit; + subdev = ProcessOneSubdev(mixer, loop, subdevJ); + if (!subdev) { + goto fail_loop_subdev; + } + loop->nbSubdevs++; + cds_list_add_tail(&subdev->list, &loop->subdevs.list); } break; + } default: AFB_ApiError(mixer->api, "%s mixer=%s hal=%s Loop=%s invalid subdevs= %s", __func__, mixer->uid, uid, loop->uid, json_object_get_string(subdevsJ)); - goto OnErrorExit; + goto fail_snd_card_ctl; } - // we may have to register up to 3 control per subdevice (vol, pause, actif) - loop->sndcard->registry = calloc(loop->scount * SMIXER_SUBDS_CTLS + 1, sizeof (RegistryEntryPcmT)); - loop->sndcard->rcount = loop->scount*SMIXER_SUBDS_CTLS; + AFB_ApiNotice(mixer->api, "%s, uid %s DONE", __func__, uid); return loop; -OnErrorExit: +fail_loop_subdev: { + AFB_ApiDebug(mixer->api, "%s cleanup", __func__); + AlsaLoopSubdevT * subdev, *tmp; + cds_list_for_each_entry_safe(subdev, tmp, &loop->subdevs.list, list) { + cds_list_del(&subdev->list); + freeSubdev(subdev); + } +} + +fail_snd_card_ctl: + snd_ctl_close(loop->sndcard->ctl); +fail_snd_card: + free(loop->sndcard); +fail_loop: + free(loop); +fail: return NULL; } -PUBLIC int ApiLoopAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ) { +static void loopDestroy(SoftMixerT * mixer, void* arg) { + AlsaSndLoopT * loop = (AlsaSndLoopT*) arg; + AFB_ApiDebug(mixer->api, "%s... %s not implemented", __func__, loop->uid); + return; - int index; - for (index = 0; index < mixer->max.loops; index++) { - if (!mixer->loops[index]) break; - } + if (loop->sndcard) { + if (loop->sndcard->ctl) { + snd_ctl_close(loop->sndcard->ctl); + free(loop->sndcard->ctl); + } + free(loop->sndcard); + } + + AlsaLoopSubdevT * subdev, *tmp; + cds_list_for_each_entry_safe(subdev, tmp, &loop->subdevs.list, list) { + cds_list_del(&subdev->list); + freeSubdev(subdev); + } + mixer->nbLoops--; + free(loop); - if (index == mixer->max.loops) { +} + +static AlsaSndLoopT * loopCreate(SoftMixerT *mixer, const char *uid, json_object *argsJ) { + AlsaSndLoopT * newLoop = AttachOneLoop(mixer, uid, argsJ); + if (!newLoop) { + goto fail; + } + + mixer->nbLoops++; + cds_list_add_tail(&newLoop->list, &mixer->loops.list); + AlsaMixerTransactionObjectAdd(mixer->transaction, newLoop, loopDestroy); + +fail: + return newLoop; +} + +PUBLIC int ApiLoopAttach(SoftMixerT *mixer, AFB_ReqT request, const char * uid, json_object * argsJ) { + + AlsaSndLoopT * newLoop = NULL; + + if (mixer->nbLoops >= mixer->max.loops) { AFB_IfReqFailF(mixer, request, "too-small", "mixer=%s hal=%s max loop=%d", mixer->uid, uid, mixer->max.loops); - goto OnErrorExit; + goto fail; } switch (json_object_get_type(argsJ)) { size_t count; case json_type_object: - mixer->loops[index] = AttachOneLoop(mixer, uid, argsJ); - if (!mixer->loops[index]) { - goto OnErrorExit; + newLoop = loopCreate(mixer, uid, argsJ); + if (!newLoop) { + goto fail; } break; case json_type_array: count = json_object_array_length(argsJ); - if (count > (mixer->max.loops - index)) { + if (count > (mixer->max.loops - mixer->nbLoops)) { AFB_IfReqFailF(mixer, request, "too-small", "mixer=%s hal=%s max loop=%d", mixer->uid, uid, mixer->max.loops); - goto OnErrorExit; - + goto fail_loop; } for (int idx = 0; idx < count; idx++) { json_object *loopJ = json_object_array_get_idx(argsJ, idx); - mixer->loops[index + idx] = AttachOneLoop(mixer, uid, loopJ); - if (!mixer->loops[index + idx]) { - goto OnErrorExit; + newLoop = loopCreate(mixer, uid, loopJ); + if (newLoop == NULL) { + goto fail_loop; } } break; default: AFB_IfReqFailF(mixer, request, "bad-loop", "mixer=%s hal=%s loops invalid argsJ= %s", mixer->uid, uid, json_object_get_string(argsJ)); - goto OnErrorExit; + goto fail; } return 0; -OnErrorExit: +fail_loop: +fail: return -1; } diff --git a/plugins/alsa/alsa-api-mixer.c b/plugins/alsa/alsa-api-mixer.c index 171046b..ae08461 100644 --- a/plugins/alsa/alsa-api-mixer.c +++ b/plugins/alsa/alsa-api-mixer.c @@ -19,58 +19,15 @@ #define _GNU_SOURCE // needed for vasprintf #include "alsa-softmixer.h" -#include "alsa-bluez.h" #include #include -extern Lua2cWrapperT Lua2cWrap; static void MixerRemoveVerb(AFB_ReqT request) { - SoftMixerT *mixer = (SoftMixerT*) afb_req_get_vcbdata(request); - int error; - - for (int idx = 0; mixer->streams[idx]->uid; idx++) { - AlsaStreamAudioT *stream = mixer->streams[idx]; - AlsaPcmCopyHandleT * copy = stream->copy; - - AFB_ApiNotice(mixer->api, "cleaning mixer=%s stream=%s", mixer->uid, stream->uid); - - error = pthread_cancel(stream->copy->rthread); - if (error) { - AFB_ReqFailF(request, "internal-error", "Fail to kill audio-stream threads mixer=%s", mixer->uid); - goto OnErrorExit; - } - - error = afb_api_del_verb(mixer->api, stream->uid, (void**)&mixer); - if (error) { - AFB_ReqFailF(request, "internal-error", "Mixer=%s fail to remove verb=%s error=%s", mixer->uid, stream->uid, strerror(error)); - goto OnErrorExit; - } - - // free audio-stream dynamic structures - snd_pcm_close(copy->pcmIn->handle); - snd_pcm_close(copy->pcmOut->handle); - if (copy->evtsrc) sd_event_source_unref(copy->evtsrc); - if (copy->sdLoop) sd_event_unref(copy->sdLoop); - } - - // // (Fulup to be Done) registry is attached to source - // if (mixer->sources) ApiSourcFree (mixer); - // if (mixer->sinks) ApiSinkFree (mixer); - // if (mixer->loops) ApiLoopFree (mixer); - // if (mixer->ramps) ApiRampFree (mixer); - // if (mixer->zones) ApiZoneFree (mixer); - - // finally free mixer handle - free(mixer); - AFB_ReqSuccess(request, NULL, "Fulup: delete might not clean everything properly"); - + AFB_ReqFailF(request, "internal-error", "Function not implemented"); return; - -OnErrorExit: - AFB_ReqFail(request, "internal-error", "fail to delete mixer"); } STATIC json_object *MixerInfoOneStream(AlsaStreamAudioT *stream, int verbose) { @@ -100,8 +57,7 @@ STATIC json_object * MixerInfoStreams(SoftMixerT *mixer, json_object *streamsJ, const char * key; json_object *valueJ; json_object *responseJ = NULL; - AlsaStreamAudioT **streams = mixer->streams; - + AlsaStreamAudioT * stream; switch (json_object_get_type(streamsJ)) { @@ -109,17 +65,18 @@ STATIC json_object * MixerInfoStreams(SoftMixerT *mixer, json_object *streamsJ, case json_type_boolean: // list every existing stream responseJ = json_object_new_array(); - for (int idx = 0; streams[idx]; idx++) { - valueJ = MixerInfoOneStream(streams[idx], verbose); + cds_list_for_each_entry(stream, &mixer->streams.list, list) { + valueJ = MixerInfoOneStream(stream, verbose); json_object_array_add(responseJ, valueJ); } break; case json_type_string: key = json_object_get_string(streamsJ); - for (int idx = 0; streams[idx]; idx++) { - if (strcasecmp(streams[idx]->uid, key)) continue; - responseJ = MixerInfoOneStream(streams[idx], verbose); + cds_list_for_each_entry(stream, &mixer->streams.list, list) { + if (strcasecmp(stream->uid, key)) + continue; + responseJ = MixerInfoOneStream(stream, verbose); break; } break; @@ -132,9 +89,10 @@ STATIC json_object * MixerInfoStreams(SoftMixerT *mixer, json_object *streamsJ, __func__, json_object_get_string(streamsJ), wrap_json_get_error_string(error), wrap_json_get_error_position(error)); goto OnErrorExit; } - for (int idx = 0; streams[idx]; idx++) { - if (strcasecmp(streams[idx]->uid, key)) continue; - responseJ = MixerInfoOneStream(streams[idx], verbose); + cds_list_for_each_entry(stream, &mixer->streams.list, list) { + if (strcasecmp(stream->uid, key)) + continue; + responseJ = MixerInfoOneStream(stream, verbose); break; } break; @@ -147,8 +105,8 @@ STATIC json_object * MixerInfoStreams(SoftMixerT *mixer, json_object *streamsJ, valueJ = MixerInfoStreams(mixer, streamJ, verbose); if (!valueJ) { AFB_ApiError(mixer->api, - "%s: fail to find stream=%s", - __func__, json_object_get_string(streamsJ)); + "%s: failed to find stream=%s", + __func__, json_object_get_string(streamJ)); goto OnErrorExit; } json_object_array_add(responseJ, valueJ); @@ -156,7 +114,7 @@ STATIC json_object * MixerInfoStreams(SoftMixerT *mixer, json_object *streamsJ, break; default: - AFB_ApiError(mixer->api, "MixerInfoStreams: unsupported json type streamsJ=%s", json_object_get_string(streamsJ)); + AFB_ApiError(mixer->api, "%s: unsupported json type streamsJ=%s", __func__, json_object_get_string(streamsJ)); goto OnErrorExit; } @@ -187,7 +145,7 @@ STATIC json_object *MixerInfoRamps(SoftMixerT *mixer, json_object *rampsJ, int v const char * key; json_object *valueJ; json_object *responseJ = NULL; - AlsaVolRampT **ramps = mixer->ramps; + AlsaVolRampT * ramp; switch (json_object_get_type(rampsJ)) { @@ -195,18 +153,18 @@ STATIC json_object *MixerInfoRamps(SoftMixerT *mixer, json_object *rampsJ, int v case json_type_boolean: // list every existing ramp responseJ = json_object_new_array(); - for (int idx = 0; ramps[idx]; idx++) { + cds_list_for_each_entry(ramp, &mixer->ramps.list, list) { - valueJ = MixerInfoOneRamp(ramps[idx], verbose); + valueJ = MixerInfoOneRamp(ramp, verbose); json_object_array_add(responseJ, valueJ); } break; case json_type_string: key = json_object_get_string(rampsJ); - for (int idx = 0; ramps[idx]; idx++) { - if (strcasecmp(ramps[idx]->uid, key)) continue; - responseJ = MixerInfoOneRamp(ramps[idx], verbose); + cds_list_for_each_entry(ramp, &mixer->ramps.list, list) { + if (strcasecmp(ramp->uid, key)) continue; + responseJ = MixerInfoOneRamp(ramp, verbose); break; } break; @@ -214,12 +172,13 @@ STATIC json_object *MixerInfoRamps(SoftMixerT *mixer, json_object *rampsJ, int v case json_type_object: error = wrap_json_unpack(rampsJ, "{ss}", "uid", &key); if (error) { - AFB_ApiError(mixer->api, "MixerInfoRamps: missing 'uid' request rampJ=%s error=%s position=%d", json_object_get_string(rampsJ), wrap_json_get_error_string(error), wrap_json_get_error_position(error)); + AFB_ApiError(mixer->api, "%s: missing 'uid' request rampJ=%s error=%s position=%d", + __func__, json_object_get_string(rampsJ), wrap_json_get_error_string(error), wrap_json_get_error_position(error)); goto OnErrorExit; } - for (int idx = 0; ramps[idx]; idx++) { - if (strcasecmp(ramps[idx]->uid, key)) continue; - responseJ = MixerInfoOneRamp(ramps[idx], verbose); + cds_list_for_each_entry(ramp, &mixer->ramps.list, list) { + if (strcasecmp(ramp->uid, key)) continue; + responseJ = MixerInfoOneRamp(ramp, verbose); break; } break; @@ -231,7 +190,7 @@ STATIC json_object *MixerInfoRamps(SoftMixerT *mixer, json_object *rampsJ, int v valueJ = MixerInfoRamps(mixer, rampJ, verbose); if (!valueJ) { - AFB_ApiError(mixer->api, "MixerInfoRamps: fail to find ramp=%s", json_object_get_string(rampsJ)); + AFB_ApiError(mixer->api, "%s: fail to find ramp=%s", __func__, json_object_get_string(rampsJ)); goto OnErrorExit; } json_object_array_add(responseJ, valueJ); @@ -239,7 +198,7 @@ STATIC json_object *MixerInfoRamps(SoftMixerT *mixer, json_object *rampsJ, int v break; default: - AFB_ApiError(mixer->api, "MixerInfoRamps: unsupported json type rampsJ=%s", json_object_get_string(rampsJ)); + AFB_ApiError(mixer->api, "%s: unsupported json type rampsJ=%s", __func__, json_object_get_string(rampsJ)); goto OnErrorExit; } @@ -252,30 +211,34 @@ OnErrorExit: STATIC json_object *MixerInfoOneZone(AlsaSndZoneT *zone, int verbose) { json_object *responseJ; + AlsaPcmChannelT * channel; + if (!verbose) { wrap_json_pack(&responseJ, "{ss}", "uid", zone->uid); } else { json_object *responseJ = json_object_new_object(); - if (zone->sinks) { + if (zone->nbSinks > 0) { json_object *sinksJ = json_object_new_array(); - for (int jdx = 0; zone->sinks[jdx]; jdx++) { + + cds_list_for_each_entry(channel, &zone->sinks.list, list) { json_object *channelJ; wrap_json_pack(&channelJ, "{ss,si}" - , "uid", zone->sinks[jdx]->uid - , "port", zone->sinks[jdx]->port + , "uid", channel->uid + , "port", channel->port ); json_object_array_add(sinksJ, channelJ); } json_object_object_add(responseJ, "sinks", sinksJ); } - if (zone->sources) { + if (zone->nbSources > 0) { json_object *sourcesJ = json_object_new_array(); - for (int jdx = 0; zone->sources[jdx]; jdx++) { + + cds_list_for_each_entry(channel, &zone->sources.list, list) { json_object *channelJ; wrap_json_pack(&channelJ, "{ss,si}" - , "uid", zone->sources[jdx]->uid - , "port", zone->sources[jdx]->port + , "uid", channel->uid + , "port", channel->port ); json_object_array_add(sourcesJ, channelJ); } @@ -300,7 +263,8 @@ STATIC json_object *MixerInfoZones(SoftMixerT *mixer, json_object *zonesJ, int v const char * key; json_object *valueJ; json_object *responseJ = NULL; - AlsaSndZoneT **zones = mixer->zones; + + AlsaSndZoneT *zone; switch (json_object_get_type(zonesJ)) { @@ -308,18 +272,18 @@ STATIC json_object *MixerInfoZones(SoftMixerT *mixer, json_object *zonesJ, int v case json_type_boolean: // list every existing zone responseJ = json_object_new_array(); - for (int idx = 0; zones[idx]; idx++) { - - valueJ = MixerInfoOneZone(zones[idx], verbose); + cds_list_for_each_entry(zone, &mixer->zones.list, list) { + valueJ = MixerInfoOneZone(zone, verbose); json_object_array_add(responseJ, valueJ); } break; case json_type_string: key = json_object_get_string(zonesJ); - for (int idx = 0; zones[idx]; idx++) { - if (strcasecmp(zones[idx]->uid, key)) continue; - responseJ = MixerInfoOneZone(zones[idx], verbose); + cds_list_for_each_entry(zone, &mixer->zones.list, list) { + if (strcasecmp(zone->uid, key)) + continue; + responseJ = MixerInfoOneZone(zone, verbose); break; } break; @@ -332,9 +296,9 @@ STATIC json_object *MixerInfoZones(SoftMixerT *mixer, json_object *zonesJ, int v __func__ ,json_object_get_string(zonesJ), wrap_json_get_error_string(error), wrap_json_get_error_position(error)); goto OnErrorExit; } - for (int idx = 0; zones[idx]; idx++) { - if (strcasecmp(zones[idx]->uid, key)) continue; - responseJ = MixerInfoOneZone(zones[idx], verbose); + cds_list_for_each_entry(zone, &mixer->zones.list, list) { + if (strcasecmp(zone->uid, key)) continue; + responseJ = MixerInfoOneZone(zone, verbose); break; } break; @@ -346,7 +310,7 @@ STATIC json_object *MixerInfoZones(SoftMixerT *mixer, json_object *zonesJ, int v valueJ = MixerInfoZones(mixer, zoneJ, verbose); if (!valueJ) { - AFB_ApiError(mixer->api, "MixerInfoZones: fail to find zone=%s", json_object_get_string(zonesJ)); + AFB_ApiError(mixer->api, "%s: fail to find zone=%s", __func__, json_object_get_string(zonesJ)); goto OnErrorExit; } json_object_array_add(responseJ, valueJ); @@ -354,11 +318,11 @@ STATIC json_object *MixerInfoZones(SoftMixerT *mixer, json_object *zonesJ, int v break; default: - AFB_ApiError(mixer->api, "MixerInfoZones: unsupported json type zonesJ=%s", json_object_get_string(zonesJ)); + AFB_ApiError(mixer->api, "%s: unsupported json type zonesJ=%s", __func__, json_object_get_string(zonesJ)); goto OnErrorExit; } - AFB_ApiNotice(mixer->api, "MixerInfoZones: response=%s", json_object_get_string(responseJ)); + AFB_ApiNotice(mixer->api, "%s: response=%s", __func__, json_object_get_string(responseJ)); return (responseJ); OnErrorExit: @@ -384,7 +348,7 @@ STATIC json_object *MixerInfoOnePcm(AlsaSndPcmT *pcm, int verbose) { wrap_json_pack(&alsaJ, "{ss,ss,so}" , "volume", pcm->volume , "mute", pcm->mute - , "ccount", pcm->ccount + , "ccount", pcm->nbChannels ); wrap_json_pack(&responseJ, "{ss,ss,so,so}" , "uid", pcm->uid @@ -401,18 +365,18 @@ STATIC json_object *MixerInfoPcms(SoftMixerT *mixer, json_object *pcmsJ, snd_pcm const char * key; json_object *valueJ; json_object *responseJ = NULL; - AlsaSndPcmT **pcms; + AlsaSndPcmT * pcms = NULL, *pcm; switch (direction) { case SND_PCM_STREAM_PLAYBACK: - pcms = mixer->sinks; + pcms = &mixer->sinks; break; case SND_PCM_STREAM_CAPTURE: - pcms = mixer->sources; + pcms = &mixer->sources; break; default: - AFB_ApiError(mixer->api, "MixerInfoPcms: invalid Direction should be SND_PCM_STREAM_PLAYBACK|SND_PCM_STREAM_capture"); + AFB_ApiError(mixer->api, "%s: invalid Direction should be SND_PCM_STREAM_PLAYBACK|SND_PCM_STREAM_capture", __func__); goto OnErrorExit; } @@ -422,18 +386,18 @@ STATIC json_object *MixerInfoPcms(SoftMixerT *mixer, json_object *pcmsJ, snd_pcm case json_type_boolean: // list every existing pcm responseJ = json_object_new_array(); - for (int idx = 0; pcms[idx]; idx++) { - - valueJ = MixerInfoOnePcm(pcms[idx], verbose); + cds_list_for_each_entry(pcm, &pcms->list, list) { + valueJ = MixerInfoOnePcm(pcm, verbose); json_object_array_add(responseJ, valueJ); } break; case json_type_string: key = json_object_get_string(pcmsJ); - for (int idx = 0; pcms[idx]; idx++) { - if (strcasecmp(pcms[idx]->uid, key)) continue; - responseJ = MixerInfoOnePcm(pcms[idx], verbose); + cds_list_for_each_entry(pcm, &pcms->list, list) { + if (strcasecmp(pcm->uid, key)) + continue; + responseJ = MixerInfoOnePcm(pcm, verbose); break; } break; @@ -446,9 +410,10 @@ STATIC json_object *MixerInfoPcms(SoftMixerT *mixer, json_object *pcmsJ, snd_pcm __func__, json_object_get_string(pcmsJ), wrap_json_get_error_string(error), wrap_json_get_error_position(error)); goto OnErrorExit; } - for (int idx = 0; pcms[idx]; idx++) { - if (strcasecmp(pcms[idx]->uid, key)) continue; - responseJ = MixerInfoOnePcm(pcms[idx], verbose); + cds_list_for_each_entry(pcm, &pcms->list, list) { + if (strcasecmp(pcm->uid, key)) + continue; + responseJ = MixerInfoOnePcm(pcm, verbose); break; } break; @@ -547,6 +512,7 @@ STATIC void MixerAttachVerb(AFB_ReqT request) { json_object *argsJ = afb_req_json(request); json_object *responseJ = json_object_new_object(); int error; + AlsaMixerTransaction * transaction = NULL; error = wrap_json_unpack(argsJ, "{ss,s?s,s?s,s?o,s?o,s?o,s?o,s?o,s?o !}" , "uid", &uid @@ -562,35 +528,43 @@ STATIC void MixerAttachVerb(AFB_ReqT request) { if (error) { AFB_ReqFailF(request, "invalid-syntax", - "mixer=%s missing 'uid|ramps|playbacks|captures|zones|streams' error=%s args=%s", - mixer->uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); - goto OnErrorExit; + "%s mixer=%s missing 'uid|ramps|playbacks|captures|zones|streams' error=%s args=%s", + __func__, mixer->uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); + goto fail; + } + + transaction = AlsaMixerTransactionNew(mixer, uid); + if (transaction == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; } + mixer->transaction = transaction; + AFB_ApiInfo(mixer->api, "%s set LOOPS", __func__); if (loopsJ) { error = ApiLoopAttach(mixer, request, uid, loopsJ); - if (error) goto OnErrorExit; + if (error) goto fail; } - AFB_ApiInfo(mixer->api, "%s set PLAYBACK", __func__); + AFB_ApiInfo(mixer->api, "%s set PLAYBACKS", __func__); if (playbacksJ) { error = ApiSinkAttach(mixer, request, uid, playbacksJ); - if (error) goto OnErrorExit; + if (error) goto fail_loop; json_object *resultJ = MixerInfoPcms(mixer, playbacksJ, SND_PCM_STREAM_PLAYBACK, 0); json_object_object_add(responseJ, "playbacks", resultJ); } - AFB_ApiInfo(mixer->api, "%s set CAPTURE", __func__); + AFB_ApiInfo(mixer->api, "%s set CAPTURES", __func__); if (capturesJ) { error = ApiSourceAttach(mixer, request, uid, capturesJ); if (error) { AFB_ApiError(mixer->api,"%s: source attach failed", __func__); - goto OnErrorExit; + goto fail_sink; } json_object *resultJ = MixerInfoPcms(mixer, capturesJ, SND_PCM_STREAM_CAPTURE, 0); @@ -601,7 +575,8 @@ STATIC void MixerAttachVerb(AFB_ReqT request) { if (zonesJ) { error = ApiZoneAttach(mixer, request, uid, zonesJ); - if (error) goto OnErrorExit; + if (error) + goto fail_source; json_object *resultJ = MixerInfoZones(mixer, zonesJ, 0); json_object_object_add(responseJ, "zone", resultJ); @@ -611,7 +586,8 @@ STATIC void MixerAttachVerb(AFB_ReqT request) { if (rampsJ) { error = ApiRampAttach(mixer, request, uid, rampsJ); - if (error) goto OnErrorExit; + if (error) + goto fail_zone; json_object *resultJ = MixerInfoRamps(mixer, rampsJ, 0); json_object_object_add(responseJ, "ramps", resultJ); @@ -621,71 +597,54 @@ STATIC void MixerAttachVerb(AFB_ReqT request) { if (streamsJ) { error = ApiStreamAttach(mixer, request, uid, prefix, streamsJ); - if (error) goto OnErrorExit; + if (error) + goto fail_ramp; json_object *resultJ = MixerInfoStreams(mixer, streamsJ, 0); json_object_object_add(responseJ, "streams", resultJ); } + error = afb_api_add_verb(mixer->api, uid, "Post Attach API", AlsaMixerTransactionVerbCB, transaction, NULL, 0, 0); + if (error) { + AFB_ApiError(mixer->api, "%s mixer=%s verb=%s fail to register post attach Verb ", + __func__, mixer->uid, uid); + goto fail_stream; + } + AFB_ApiNotice(mixer->api, "%s responseJ=%s", __func__, json_object_get_string(responseJ)); AFB_ReqSuccess(request, responseJ, NULL); AFB_ApiInfo(mixer->api,"%s DONE", __func__); return; -OnErrorExit: +fail_stream: + // TODO remove created streams +fail_ramp: + // TODO remove created ramps +fail_zone: + // TODO remove created zone +fail_source: + // TODO remove created sources +fail_sink: + // TODO remove created sinks +fail_loop: + // TODO remove created loops +fail: + + if (mixer->transaction) + free(mixer->transaction); + AFB_ApiError(mixer->api,"%s FAILED", __func__); return; } -static void MixerBluezAlsaDevVerb(AFB_ReqT request) { - SoftMixerT *mixer = (SoftMixerT*) afb_req_get_vcbdata(request); - char * interface = NULL, *device = NULL, *profile = NULL; - json_object *argsJ = afb_req_json(request); - int error; - - if (json_object_is_type(argsJ,json_type_null)) { - goto parsed; - } - - error = wrap_json_unpack(argsJ, "{ss,ss,ss !}" - , "interface", &interface - , "device", &device - , "profile", &profile - ); - - if (error) { - AFB_ReqFailF(request, - "invalid-syntax", - "mixer=%s missing 'interface|device|profile' error=%s args=%s", - mixer->uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); - goto OnErrorExit; - } - -parsed: - AFB_ApiNotice(mixer->api, "%s: interface %s, device %s, profile %s\n", __func__, interface, device, profile); - error = alsa_bluez_set_remote_device(interface, device, profile); - if (error) { - AFB_ReqFailF(request, - "runtime error", - "Unable to set device , err %d", error); - goto OnErrorExit; - } - - AFB_ReqSuccess(request, NULL, NULL); - -OnErrorExit: - return; -} - // Every HAL export the same API & Interface Mapping from SndCard to AudioLogic is done through alsaHalSndCardT STATIC AFB_ApiVerbs CtrlApiVerbs[] = { /* VERB'S NAME FUNCTION TO CALL SHORT DESCRIPTION */ { .verb = "attach", .callback = MixerAttachVerb, .info = "attach resources to mixer"}, { .verb = "remove", .callback = MixerRemoveVerb, .info = "remove existing mixer streams, zones, ..."}, { .verb = "info", .callback = MixerInfoVerb, .info = "list existing mixer streams, zones, ..."}, - { .verb = "bluezalsa_dev", .callback = MixerBluezAlsaDevVerb, .info = "set bluez alsa device"}, { .verb = NULL} /* marker for end of the array */ }; @@ -704,6 +663,8 @@ CTLP_CAPI(MixerAttach, source, argsJ, responseJ) { json_object *playbackJ = NULL, *captureJ = NULL, *zonesJ = NULL, *streamsJ = NULL, *rampsJ = NULL, *loopsJ = NULL; const char* uid = source->uid, *prefix = NULL; + AlsaMixerTransaction * transaction = NULL; + int error; error = wrap_json_unpack(argsJ, "{s?s, s?o,s?o,s?o,s?o,s?o,s?o !}" @@ -716,10 +677,17 @@ CTLP_CAPI(MixerAttach, source, argsJ, responseJ) { , "streams", &streamsJ ); if (error) { - AFB_ApiError(mixer->api, "MixerAttachVerb: invalid-syntax mixer=%s error=%s args=%s", mixer->uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); + AFB_ApiError(mixer->api, "%s: invalid-syntax mixer=%s error=%s args=%s", + __func__, mixer->uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); goto OnErrorExit; } + transaction = AlsaMixerTransactionNew(mixer, uid); + if (transaction == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto OnErrorExit; + } + if (playbackJ) { error = ApiSinkAttach(mixer, NULL, uid, playbackJ); if (error) goto OnErrorExit; @@ -759,6 +727,10 @@ OnErrorExit: CTLP_CAPI(MixerCreate, source, argsJ, responseJ) { SoftMixerT *mixer = calloc(1, sizeof (SoftMixerT)); + if (mixer == NULL) { + SOFTMIXER_NOMEM(source->api); + goto fail; + } source->context = mixer; int error; @@ -770,8 +742,8 @@ CTLP_CAPI(MixerCreate, source, argsJ, responseJ) { mixer->max.ramps = SMIXER_DEFLT_RAMPS; if (json_object_get_type(argsJ) != json_type_object) { - AFB_ApiError(source->api, "_mixer_new_: invalid object type= %s", json_object_get_string(argsJ)); - goto OnErrorExit; + AFB_ApiError(source->api, "%s: invalid object type= %s", __func__, json_object_get_string(argsJ)); + goto fail; } error = wrap_json_unpack(argsJ, "{ss,s?s,s?i,s?i,s?i,s?i,s?i,s?i !}" @@ -785,31 +757,57 @@ CTLP_CAPI(MixerCreate, source, argsJ, responseJ) { , "max_ramp", &mixer->max.ramps ); if (error) { - AFB_ApiNotice(source->api, "_mixer_new_ missing 'uid|max_loop|max_sink|max_source|max_zone|max_stream|max_ramp' error=%s mixer=%s", wrap_json_get_error_string(error), json_object_get_string(argsJ)); - goto OnErrorExit; + AFB_ApiNotice(source->api, + "%s missing 'uid|max_loop|max_sink|max_source|max_zone|max_stream|max_ramp' error=%s mixer=%s", + __func__, wrap_json_get_error_string(error), json_object_get_string(argsJ)); + goto fail_mixer; } // make sure string do not get deleted mixer->uid = strdup(mixer->uid); - if (mixer->info)mixer->info = strdup(mixer->info); - - mixer->loops = calloc(mixer->max.loops + 1, sizeof (void*)); - mixer->sinks = calloc(mixer->max.sinks + 1, sizeof (void*)); - mixer->sources = calloc(mixer->max.sources + 1, sizeof (void*)); - mixer->zones = calloc(mixer->max.zones + 1, sizeof (void*)); - mixer->streams = calloc(mixer->max.streams + 1, sizeof (void*)); - mixer->ramps = calloc(mixer->max.ramps + 1, sizeof (void*)); + if (mixer->uid == NULL) { + SOFTMIXER_NOMEM(source->api); + goto fail_mixer; + } + if (mixer->info) { + mixer->info = strdup(mixer->info); + if (mixer->info == NULL) { + SOFTMIXER_NOMEM(source->api); + goto fail_uid; + } + } + + mixer->nbLoops = 0; + mixer->nbSinks = 0; + mixer->nbSources = 0; + mixer->nbZones = 0; + mixer->nbStreams = 0; + mixer->nbZones = 0; + + CDS_INIT_LIST_HEAD(&mixer->loops.list); + CDS_INIT_LIST_HEAD(&mixer->sinks.list); + CDS_INIT_LIST_HEAD(&mixer->sources.list); + CDS_INIT_LIST_HEAD(&mixer->zones.list); + CDS_INIT_LIST_HEAD(&mixer->streams.list); + CDS_INIT_LIST_HEAD(&mixer->ramps.list); mixer->sdLoop = AFB_GetEventLoop(source->api); mixer->api = source->api; afb_api_set_userdata(source->api, mixer); error = LoadStaticVerbs(mixer, CtrlApiVerbs); - if (error) goto OnErrorExit; + if (error) + goto fail_info; return 0; -OnErrorExit: +fail_info: + free((char*)mixer->info); +fail_uid: + free((char*)mixer->uid); +fail_mixer: + free(mixer); +fail: return -1; } diff --git a/plugins/alsa/alsa-api-pcm.c b/plugins/alsa/alsa-api-pcm.c index 1bba75c..85e3c3a 100644 --- a/plugins/alsa/alsa-api-pcm.c +++ b/plugins/alsa/alsa-api-pcm.c @@ -51,17 +51,31 @@ typedef struct { AlsaSndPcmT* pcm; } apiVerbHandleT; -STATIC AlsaPcmChannelT *ProcessOneChannel(SoftMixerT *mixer, const char *uid, json_object *argsJ) { +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) goto OnErrorExit; + 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; -OnErrorExit: - AFB_ApiError(mixer->api, "ProcessOneChannel: sndcard=%s channel: missing (uid||port) error=%s json=%s", uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); +fail_channel: free(channel); +fail: return NULL; } @@ -81,37 +95,45 @@ STATIC int PcmAttachOneCtl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, json_object 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 OnErrorExit; + goto fail; } if (numid > 0) { elemId = AlsaCtlGetNumidElemId(mixer, sndcard, numid); if (!elemId) { - AFB_ApiError(mixer->api, "PcmAttachOneCtl sndard=%s fail to find control numid=%d", sndcard->cid.cardid, numid); - goto OnErrorExit; + 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, "PcmAttachOneCtl sndard=%s fail to find control name=%s", sndcard->cid.cardid, name); - goto OnErrorExit; + 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, "PcmAttachOneCtl: sndard=%s numid=%d name='%s' not loadable", sndcard->cid.cardid, control->numid, control->name); - goto OnErrorExit; + 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, "PcmAttachOneCtl: sndard=%s numid=%d name='%s' not writable", sndcard->cid.cardid, control->numid, control->name); - goto OnErrorExit; + 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); @@ -132,22 +154,25 @@ STATIC int PcmAttachOneCtl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, json_object break; default: - AFB_ApiError(mixer->api, "PcmAttachOneCtl: 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; + 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, "PcmAttachOneCtl: sndard=%s numid=%d name='%s' not writable", sndcard->cid.cardid, control->numid, control->name); - goto OnErrorExit; + 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; -OnErrorExit: - if (elemId)free(elemId); +fail_control_name: + free((char*)control->name); +fail_elemId: + free(elemId); +fail: return -1; } @@ -161,7 +186,7 @@ STATIC int PcmSetControl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaSndControl elemId = AlsaCtlGetNumidElemId(mixer, sndcard, control->numid); if (!elemId) { - AFB_ApiError(mixer->api, "PcmSetControl sndard=%s fail to find control numid=%d", sndcard->cid.cardid, control->numid); + AFB_ApiError(mixer->api, "%s sndard=%s fail to find control numid=%d", __func__, sndcard->cid.cardid, control->numid); goto OnErrorExit; } @@ -169,18 +194,18 @@ STATIC int PcmSetControl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaSndControl snd_ctl_elem_info_set_id(elemInfo, elemId); if (snd_ctl_elem_info(sndcard->ctl, elemInfo) < 0) { - AFB_ApiError(mixer->api, "PcmSetControl: sndard=%s numid=%d name='%s' not loadable", sndcard->cid.cardid, control->numid, control->name); + 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, "PcmSetControl: sndard=%s numid=%d name='%s' not writable", sndcard->cid.cardid, control->numid, control->name); + 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, "PcmSetControl sndard=%s fail to read control numid=%d", sndcard->cid.cardid, control->numid); + AFB_ApiError(mixer->api, "%s sndcard=%s fail to read control numid=%d", __func__, sndcard->cid.cardid, control->numid); goto OnErrorExit; } @@ -347,9 +372,19 @@ OnErrorExit: return; } +PUBLIC void ApiPcmDelParams(SoftMixerT* mixer, AlsaPcmHwInfoT* params) { + free((char*)params->formatS); + free(params); +} + PUBLIC AlsaPcmHwInfoT * ApiPcmSetParams(SoftMixerT *mixer, const char *uid, json_object * paramsJ) { - AlsaPcmHwInfoT *params = calloc(1, sizeof (AlsaPcmHwInfoT)); + 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; @@ -358,23 +393,31 @@ PUBLIC AlsaPcmHwInfoT * ApiPcmSetParams(SoftMixerT *mixer, const char *uid, json if (paramsJ) { int error = - wrap_json_unpack(paramsJ, "{s?i,s?i, s?s, s?s !}", - "rate", ¶ms->rate, - "channels",¶ms->channels, - "format", &format, + wrap_json_unpack(paramsJ, "{s?i,s?s,s?s !}", + "rate", ¶ms->rate, + "format", &format, "access", &access); if (error) { - AFB_ApiError(mixer->api, "ApiPcmSetParams: sndcard=%s invalid params=%s", uid, json_object_get_string(paramsJ)); - goto OnErrorExit; + AFB_ApiError(mixer->api, "%s: sndcard=%s invalid params=%s", + __func__, uid, json_object_get_string(paramsJ)); + goto fail_params; } } - if (!format) { + if (!format) { params->format = SND_PCM_FORMAT_S16_LE; - params->formatS = "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); @@ -394,11 +437,11 @@ PUBLIC AlsaPcmHwInfoT * ApiPcmSetParams(SoftMixerT *mixer, const char *uid, json FORMAT_CHECK(FLOAT_LE); FORMAT_CHECK(FLOAT_BE); - AFB_ApiError(mixer->api, "ApiPcmSetParams:%s(params) unsupported format 'S16_LE|S32_L|...' format=%s", uid, format); - goto OnErrorExit; + 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, "ApiPcmSetParams:%s format set to SND_PCM_FORMAT_%s", uid, params->formatS); + 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;} @@ -413,25 +456,97 @@ check_access: ACCESS_CHECK(RW_INTERLEAVED); ACCESS_CHECK(RW_NONINTERLEAVED); - AFB_ApiNotice(mixer->api, "ApiPcmSetParams:%s(params) unsupported access 'RW_INTERLEAVED|MMAP_INTERLEAVED|MMAP_COMPLEX' access=%s", uid, access); - goto OnErrorExit; + 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, "ApiPcmSetParams:%s access set to %s", uid, access); + AFB_ApiNotice(mixer->api, "%s:%s access set to %s", __func__, uid, access); return params; -OnErrorExit: +fail_params: free(params); +fail: return NULL; } -PUBLIC AlsaSndPcmT * ApiPcmAttachOne(SoftMixerT *mixer, const char *uid, snd_pcm_stream_t direction, json_object * argsJ) { + +static void pcmChannelsDestroy(SoftMixerT * mixer, AlsaSndPcmT * pcm) { + AFB_ApiDebug(mixer->api, "%s: pcm %s (%d channels)", __func__, pcm->uid, pcm->nbChannels); + + AlsaPcmChannelT * channel, *tmp; + cds_list_for_each_entry_safe(channel, tmp, &pcm->channels.list, list) { + cds_list_del(&channel->list); + AFB_ApiDebug(mixer->api, "%s: del channel %s", __func__, channel->uid); + 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); + + 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 @@ -444,42 +559,40 @@ PUBLIC AlsaSndPcmT * ApiPcmAttachOne(SoftMixerT *mixer, const char *uid, snd_pcm , "params", ¶msJ ); if (error) { - AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s missing 'uid|path|cardid|device|sink|source|params' error=%s args=%s", uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); - goto OnErrorExit; + 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, "ApiPcmAttachOne: hal=%s Fail to open sndcard uid=%s devpath=%s cardid=%s", uid, pcm->uid, pcm->sndcard->cid.devpath, pcm->sndcard->cid.cardid); - goto OnErrorExit; - } - - // check sndcard accepts params - pcm->sndcard->params = ApiPcmSetParams(mixer, pcm->uid, paramsJ); - if (!pcm->sndcard->params) { - AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s Fail to set params sndcard uid=%s params=%s", uid, pcm->uid, json_object_get_string(paramsJ)); - goto OnErrorExit; + 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; } if (direction == SND_PCM_STREAM_PLAYBACK) { if (!sinkJ) { - AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s SND_PCM_STREAM_PLAYBACK require sinks args=%s", uid, json_object_get_string(argsJ)); - goto OnErrorExit; + 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, "ApiPcmAttachOne: hal=%s SND_PCM_STREAM_CAPTURE require sources args=%s", uid, json_object_get_string(argsJ)); - goto OnErrorExit; + 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; - - // we may have to register SMIXER_SUBDS_CTLS per subdev (Fulup ToBeDone when sndcard get multiple device/subdev) - pcm->sndcard->registry = calloc(SMIXER_SUBDS_CTLS + 1, sizeof (RegistryEntryPcmT)); - pcm->sndcard->rcount = SMIXER_SUBDS_CTLS; } json_object *channelsJ = NULL, *controlsJ = NULL; @@ -488,33 +601,56 @@ PUBLIC AlsaSndPcmT * ApiPcmAttachOne(SoftMixerT *mixer, const char *uid, snd_pcm , "controls", &controlsJ ); if (error) { - AFB_ApiNotice(mixer->api, "ApiPcmAttachOne: hal=%s pcms missing channels|[controls] error=%s paybacks=%s", uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); - goto OnErrorExit; + 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: - pcm->ccount = 1; - pcm->channels = calloc(2, sizeof (void*)); - pcm->channels[0] = ProcessOneChannel(mixer, pcm->uid, channelsJ); - if (!pcm->channels[0]) goto OnErrorExit; + channel = ProcessOneChannel(mixer, pcm->uid, channelsJ); + if (channel == NULL) { + goto fail_pcm_channels; + } + + pcm->nbChannels++; + cds_list_add_tail(&channel->list, &pcm->channels.list); + break; - case json_type_array: - pcm->ccount = (int) json_object_array_length(channelsJ); - pcm->channels = calloc(pcm->ccount + 1, sizeof (void*)); - for (int idx = 0; idx < pcm->ccount; idx++) { + 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); - pcm->channels[idx] = ProcessOneChannel(mixer, pcm->uid, channelJ); - if (!pcm->channels[idx]) goto OnErrorExit; + 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, "ApiPcmAttachOne:%s invalid pcm=%s", pcm->uid, json_object_get_string(channelsJ)); - goto OnErrorExit; + AFB_ApiError(mixer->api, "%s:%s invalid pcm=%s", pcm->uid, __func__, json_object_get_string(channelsJ)); + goto fail_pcm_sndcard_ctl; } } + // 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; + } + + pcm->sndcard->params->channels = pcm->nbChannels; + if (controlsJ) { json_object *volJ = NULL, *muteJ = NULL; error = wrap_json_unpack(controlsJ, "{s?o,s?o !}" @@ -525,50 +661,101 @@ PUBLIC AlsaSndPcmT * ApiPcmAttachOne(SoftMixerT *mixer, const char *uid, snd_pcm 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 OnErrorExit; + goto fail_pcm_channels; } - if (volJ) error += PcmAttachOneCtl(mixer, pcm->sndcard, volJ, &pcm->volume); - if (muteJ) error += PcmAttachOneCtl(mixer, pcm->sndcard, muteJ, &pcm->mute); - if (error) goto OnErrorExit; + 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) - goto OnErrorExit; - if (asprintf(&apiInfo, "HAL:%s SND_PCM_STREAM_PLAYBACK", uid) == -1) - goto OnErrorExit; + 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) - goto OnErrorExit; - if (asprintf(&apiInfo, "HAL:%s SND_PCM_STREAM_PLAYBACK", uid) == -1) - goto OnErrorExit; + 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, "ApiPcmAttachOne mixer=%s verb=%s fail to Register Master control ", mixer->uid, apiVerb); - goto OnErrorExit; + 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 = "none"; + 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; -OnErrorExit: - free(pcm); - free(apiVerb); - free(apiInfo); +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; } diff --git a/plugins/alsa/alsa-api-ramp.c b/plugins/alsa/alsa-api-ramp.c index 21ec165..060c917 100644 --- a/plugins/alsa/alsa-api-ramp.c +++ b/plugins/alsa/alsa-api-ramp.c @@ -28,20 +28,22 @@ PUBLIC AlsaVolRampT *ApiRampGetByUid(SoftMixerT *mixer, const char *uid) { AlsaVolRampT *ramp = NULL; - - // Loop on every Register zone pcm and extract (cardid) from (uid) - for (int idx = 0; mixer->ramps[idx]->uid != NULL; idx++) { - if (!strcasecmp(mixer->ramps[idx]->uid, uid)) { - ramp = mixer->ramps[idx]; - return ramp; - } + cds_list_for_each_entry(ramp, &mixer->ramps.list, list) { + if (!strcasecmp(ramp->uid, uid)) + return ramp; } return NULL; } STATIC AlsaVolRampT *AttachOneRamp(SoftMixerT *mixer, const char *uid, json_object *rampJ) { - const char*rampUid; + const char * rampUid; AlsaVolRampT *ramp = calloc(1, sizeof (AlsaVolRampT)); + if (ramp == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } + + CDS_INIT_LIST_HEAD(&ramp->list); int error = wrap_json_unpack(rampJ, "{ss,si,si,si !}" , "uid", &rampUid @@ -50,65 +52,87 @@ STATIC AlsaVolRampT *AttachOneRamp(SoftMixerT *mixer, const char *uid, json_obje , "down", &ramp->stepDown ); if (error) { - AFB_ApiError(mixer->api, "AttachOneRamp mixer=%s hal=%s error=%s json=%s", mixer->uid, uid, wrap_json_get_error_string(error), json_object_get_string(rampJ)); - goto OnErrorExit; + AFB_ApiError(mixer->api, + "%s mixer=%s hal=%s error=%s json=%s", + __func__, mixer->uid, uid, wrap_json_get_error_string(error), json_object_get_string(rampJ)); + goto fail_ramp; } ramp->delay = ramp->delay * 1000; // move from ms to us ramp->uid = strdup(rampUid); + if (ramp->uid == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto fail_ramp; + } + return ramp; -OnErrorExit: +fail_ramp: free(ramp); +fail: return NULL; } -PUBLIC int ApiRampAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object *argsJ) { - int index; - for (index = 0; index < mixer->max.ramps; index++) { - if (!mixer->ramps[index]) break; - } +static void rampDestroy(SoftMixerT * mixer, void * arg) { + AlsaVolRampT * ramp = (AlsaVolRampT*) arg; + AFB_ApiDebug(mixer->api, "%s... %s not implemented", __func__, ramp->uid); +} - if (index == mixer->max.ramps) { - AFB_ReqFailF(request, "too-small", "mixer=%s hal=%s max ramp=%d", mixer->uid, uid, mixer->max.ramps); - goto OnErrorExit; +static AlsaVolRampT * rampCreate(SoftMixerT * mixer, const char * uid, json_object *argsJ) { + AlsaVolRampT * newRamp = AttachOneRamp(mixer, uid, argsJ); + if (!newRamp) { + goto fail; + } + mixer->nbRamps++; + cds_list_add_tail(&newRamp->list, &mixer->ramps.list); + AlsaMixerTransactionObjectAdd(mixer->transaction, newRamp, rampDestroy); +fail: + return newRamp; +} + +PUBLIC int ApiRampAttach(SoftMixerT *mixer, AFB_ReqT request, const char * uid, json_object *argsJ) { + + if (mixer->nbRamps >= mixer->max.ramps) { + AFB_ReqFailF(request, "too-small", "mixer=%s hal=%s max ramp=%d", mixer->uid, uid, mixer->max.ramps); + goto fail; } + AlsaVolRampT * newRamp = NULL; + switch (json_object_get_type(argsJ)) { long count; case json_type_object: - mixer->ramps[index] = AttachOneRamp(mixer, uid, argsJ); - if (!mixer->ramps[index]) { + newRamp = rampCreate(mixer, uid, argsJ); + if (!newRamp) { AFB_ReqFailF(request, "bad-ramp", "mixer=%s hal=%s invalid ramp= %s", mixer->uid, 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.ramps - index)) { + if (count > (mixer->max.ramps - mixer->nbRamps)) { AFB_ReqFailF(request, "too-small", "mixer=%s hal=%s max ramp=%d", mixer->uid, uid, mixer->max.ramps); - goto OnErrorExit; - + goto fail; } for (int idx = 0; idx < count; idx++) { json_object *streamAudioJ = json_object_array_get_idx(argsJ, idx); - mixer->ramps[index + idx] = AttachOneRamp(mixer, uid, streamAudioJ); - if (!mixer->ramps[index + idx]) { - AFB_ReqFailF(request, "bad-ramp", "mixer=%s hal=%s invalid ramp= %s", mixer->uid, uid, json_object_get_string(streamAudioJ)); - goto OnErrorExit; + newRamp = rampCreate(mixer, uid, streamAudioJ); + if (!newRamp) { + AFB_ReqFailF(request, "bad-ramp", "mixer=%s hal=%s invalid ramp= %s", mixer->uid, uid, json_object_get_string(argsJ)); + goto fail; } } break; default: AFB_ReqFailF(request, "invalid-syntax", "mixer=%s hal=%s ramps invalid argsJ= %s", mixer->uid, uid, json_object_get_string(argsJ)); - goto OnErrorExit; + goto fail; } return 0; -OnErrorExit: +fail: return -1; } diff --git a/plugins/alsa/alsa-api-sink.c b/plugins/alsa/alsa-api-sink.c index 96437f5..a1137c5 100644 --- a/plugins/alsa/alsa-api-sink.c +++ b/plugins/alsa/alsa-api-sink.c @@ -24,106 +24,135 @@ PUBLIC AlsaPcmHwInfoT *ApiSinkGetParamsByZone(SoftMixerT *mixer, const char *tar // try to attach a zone as stream playback sink AlsaSndZoneT *zone = ApiZoneGetByUid(mixer, target); - if (zone && zone->sinks) { - // use 1st channel to find attached sound card. - const char *channel = zone->sinks[0]->uid; - - // search for channel uid into mixer sinks and derive sound card - for (int idx = 0; mixer->sinks[idx]; idx++) { - for (int jdx = 0; jdx < mixer->sinks[idx]->ccount; jdx++) { - if (mixer->sinks[idx]->channels[jdx]->uid && !strcasecmp(channel, mixer->sinks[idx]->channels[jdx]->uid)) { - return mixer->sinks[idx]->sndcard->params; - } - } - } + if (!zone) { + AFB_ApiError(mixer->api, "%s: Unable to find zone %s", __func__, target); + goto fail; + } + if (zone->nbSinks == 0) { + AFB_ApiError(mixer->api, "%s: Zone %s has no sinks", __func__, target); + goto fail; + } + + // use 1st channel to find attached sound card. + AlsaPcmChannelT * pcmChannel = cds_list_first_entry(&zone->sinks.list, AlsaPcmChannelT , list); + const char *channel = pcmChannel->uid; + + AFB_ApiDebug(mixer->api, "%s: first channel uid is %s, zone has %d sinks", __func__, channel, zone->nbSinks); + + AlsaSndPcmT * sink = NULL; + + cds_list_for_each_entry(sink, &mixer->sinks.list, list) { + AlsaPcmChannelT * pcmChannel; + cds_list_for_each_entry(pcmChannel, &sink->channels.list, list) { + if (pcmChannel->uid && !strcasecmp(channel, pcmChannel->uid)) { + AFB_ApiDebug(mixer->api, "%s: found %s, return", __func__, pcmChannel->uid); + return sink->sndcard->params; + } + } } + +fail: return NULL; } PUBLIC AlsaSndPcmT *ApiSinkGetByUid(SoftMixerT *mixer, const char *target) { // if no attached zone found, then try direct sink attachment - for (int idx = 0; mixer->sinks[idx]; idx++) { - if (mixer->sinks[idx]->uid && !strcasecmp(mixer->sinks[idx]->uid, target)) { - return mixer->sinks[idx]; - } - } + AlsaSndPcmT * sink; + cds_list_for_each_entry(sink, &mixer->sinks.list, list) { + if (sink->uid && !strcasecmp(sink->uid, target)) + return sink; + } return NULL; } -PUBLIC int ApiSinkAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ) { +static void sinkDestroy(SoftMixerT* mixer, void * arg) { + AlsaSndPcmT * sink = (AlsaSndPcmT*) arg; + AFB_ApiDebug(mixer->api, "%s... %s", __func__, sink->uid); - int index; - char *dmixUid = NULL; + cds_list_del(&sink->list); + mixer->nbSinks--; + ApiPcmDelete(mixer, sink); +} - for (index = 0; index < mixer->max.sinks; index++) { - if (!mixer->sinks[index]) break; +AlsaSndPcmT * sinkCreate(SoftMixerT * mixer, AFB_ReqT request, const char * uid, json_object * argsJ) { + AlsaPcmCtlT* dmixConfig = NULL; + char *dmixUid = NULL; + AlsaSndPcmT * newSink = ApiPcmAttachOne(mixer, uid, SND_PCM_STREAM_PLAYBACK, argsJ); + if (!newSink) { + goto fail; + } + /* DMIX only supports REAL hardware */ + if (!newSink->isPcmPlug) { + // move from hardware to DMIX attach to sndcard + if (asprintf(&dmixUid, "dmix-%s", newSink->uid) == -1) { + SOFTMIXER_NOMEM(mixer->api); + goto fail_pcm; + } + dmixConfig = AlsaCreateDmix(mixer, dmixUid, newSink, 0); + if (!dmixConfig) { + AFB_ReqFailF(request, "internal-error", "mixer=%s sink=%s fail to create DMIX config", mixer->uid, newSink->uid); + goto fail_dmix; + } } - if (index == mixer->max.sinks) { + mixer->nbSinks++; + cds_list_add_tail(&newSink->list, &mixer->sinks.list); + AlsaMixerTransactionObjectAdd(mixer->transaction, newSink, sinkDestroy); + + return newSink; +fail: +fail_pcm: + // TODO delete PCM +fail_dmix: + free(dmixUid); + return NULL; +} + + +PUBLIC int ApiSinkAttach(SoftMixerT *mixer, AFB_ReqT request, const char * uid, json_object * argsJ) { + + if (mixer->nbSinks >= mixer->max.sinks) { AFB_ReqFailF(request, "too-small", "mixer=%s max sink=%d", mixer->uid, mixer->max.sinks); - goto OnErrorExit; + goto fail; } switch (json_object_get_type(argsJ)) { - long count; - - AlsaPcmCtlT* dmixConfig; + long count; - case json_type_object: - mixer->sinks[index] = ApiPcmAttachOne(mixer, uid, SND_PCM_STREAM_PLAYBACK, argsJ); - if (!mixer->sinks[index]) { + case json_type_object: { + AlsaSndPcmT * newSink = sinkCreate(mixer, request, uid, argsJ); + if (!newSink) { AFB_ReqFailF(request, "bad-pcm", "mixer=%s invalid sink= %s", mixer->uid, json_object_get_string(argsJ)); - goto OnErrorExit; - } - - // move from hardware to DMIX attach to sndcard - if (asprintf(&dmixUid, "dmix-%s", mixer->sinks[index]->uid) == -1) - goto OnErrorExit; - - dmixConfig = AlsaCreateDmix(mixer, dmixUid, mixer->sinks[index], 0); - if (!dmixConfig) { - AFB_ReqFailF(request, "internal-error", "mixer=%s sink=%s fail to create DMIX config", mixer->uid, mixer->sinks[index]->uid); - goto OnErrorExit; + goto fail; } - break; - - case json_type_array: + } + case json_type_array: { count = json_object_array_length(argsJ); - if (count > (mixer->max.sinks - index)) { + if (count > (mixer->max.sinks - mixer->nbSinks)) { AFB_ReqFailF(request, "too-small", "mixer=%s max sink=%d", mixer->uid, mixer->max.sinks); - goto OnErrorExit; + goto fail; } for (int idx = 0; idx < count; idx++) { json_object *sinkJ = json_object_array_get_idx(argsJ, idx); - AlsaSndPcmT * pcm = ApiPcmAttachOne(mixer, uid, SND_PCM_STREAM_PLAYBACK, sinkJ); - if (!pcm) { + AlsaSndPcmT * newSink = sinkCreate(mixer, request, uid, sinkJ); + if (!newSink) { AFB_ReqFailF(request, "bad-pcm", "mixer=%s invalid sink= %s", mixer->uid, json_object_get_string(sinkJ)); - goto OnErrorExit; + goto fail; } - // move from hardware to DMIX attach to sndcard - if (asprintf(&dmixUid, "dmix-%s", pcm->uid) == -1) - goto OnErrorExit; - - dmixConfig = AlsaCreateDmix(mixer, dmixUid, pcm, 0); - if (!dmixConfig) { - AFB_ReqFailF(request, "internal-error", "mixer=%s sink=%s fail to create DMIX config", mixer->uid, pcm->uid); - goto OnErrorExit; - } - mixer->sinks[index + idx] = pcm; } break; + } default: AFB_ReqFailF(request, "invalid-syntax", "mixer=%s sinks invalid argsJ= %s", mixer->uid, json_object_get_string(argsJ)); - goto OnErrorExit; + goto fail; } return 0; -OnErrorExit: - free(dmixUid); +fail: return -1; } diff --git a/plugins/alsa/alsa-api-source.c b/plugins/alsa/alsa-api-source.c index 4f356b2..d227eab 100644 --- a/plugins/alsa/alsa-api-source.c +++ b/plugins/alsa/alsa-api-source.c @@ -23,62 +23,88 @@ PUBLIC AlsaSndCtlT *ApiSourceFindSubdev(SoftMixerT *mixer, const char *target) { - // search for subdev into every registered source - for (int idx = 0; mixer->sources[idx]; idx++) { - if (mixer->sources[idx]->uid && !strcasecmp(mixer->sources[idx]->uid, target)) { - return mixer->sources[idx]->sndcard; - } - } + // search for subdev into every registered source in the mixer + + AlsaSndPcmT * source; + cds_list_for_each_entry(source, &mixer->sources.list, list) { + if (source->uid && !strcmp(source->uid, target)) + return source->sndcard; + } + return NULL; } -PUBLIC int ApiSourceAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ) { +static void sourceDestroy(SoftMixerT* mixer, void * arg) { + AlsaSndPcmT * source = (AlsaSndPcmT*) arg; + AFB_ApiDebug(mixer->api, "%s... %s", __func__, source->uid); - int index; - for (index = 0; index < mixer->max.sources; index++) { - if (!mixer->sources[index]) break; - } + cds_list_del(&source->list); + mixer->nbSources--; + ApiPcmDelete(mixer, source); + + AFB_ApiDebug(mixer->api, "%s... DONE", __func__); +} + +static AlsaSndPcmT * sourceCreate(SoftMixerT* mixer, const char * uid, json_object * argsJ) { + AlsaSndPcmT * newSource = NULL; + newSource = ApiPcmAttachOne(mixer, uid, SND_PCM_STREAM_CAPTURE, argsJ); + if (!newSource) { + goto fail; + } + + mixer->nbSources++; + cds_list_add(&newSource->list, &mixer->sources.list); + AlsaMixerTransactionObjectAdd(mixer->transaction, newSource, sourceDestroy); - if (index == mixer->max.sources) { +fail: + return newSource; +} + +PUBLIC int ApiSourceAttach(SoftMixerT *mixer, AFB_ReqT request, const char * uid, json_object * argsJ) { + + if (mixer->nbSources >= mixer->max.sources) { AFB_ReqFailF(request, "too-small", "mixer=%s max source=%d", mixer->uid, mixer->max.sources); - goto OnErrorExit; + goto fail; } + AlsaSndPcmT * newSource = NULL; + switch (json_object_get_type(argsJ)) { long count; case json_type_object: - mixer->sources[index] = ApiPcmAttachOne(mixer, uid, SND_PCM_STREAM_CAPTURE, argsJ); - if (!mixer->sources[index]) { + newSource = sourceCreate(mixer, uid, argsJ); + if (!newSource) { AFB_ReqFailF(request, "bad-pcm", "mixer=%s invalid source= %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.sources - index)) { + if (count > (mixer->max.sources - mixer->nbSources)) { AFB_ReqFailF(request, "too-small", "mixer=%s max source=%d", mixer->uid, mixer->max.sources); - goto OnErrorExit; - + goto fail; } for (int idx = 0; idx < count; idx++) { json_object *sourceJ = json_object_array_get_idx(argsJ, idx); - mixer->sources[index + idx] = ApiPcmAttachOne(mixer, uid, SND_PCM_STREAM_CAPTURE, sourceJ); - if (!mixer->sources[index + idx]) { + newSource = sourceCreate(mixer, uid, sourceJ); + if (!newSource) { AFB_ReqFailF(request, "bad-pcm", "mixer=%s invalid source= %s", mixer->uid, json_object_get_string(sourceJ)); - goto OnErrorExit; + goto fail; } } + break; default: AFB_ReqFailF(request, "invalid-syntax", "mixer=%s sources invalid argsJ= %s", mixer->uid, json_object_get_string(argsJ)); - goto OnErrorExit; + goto fail; } return 0; -OnErrorExit: +fail: return -1; } 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 #include @@ -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; } diff --git a/plugins/alsa/alsa-api-zones.c b/plugins/alsa/alsa-api-zones.c index c8ab107..bf3bece 100644 --- a/plugins/alsa/alsa-api-zones.c +++ b/plugins/alsa/alsa-api-zones.c @@ -25,166 +25,261 @@ extern Lua2cWrapperT Lua2cWrap; PUBLIC AlsaSndZoneT *ApiZoneGetByUid(SoftMixerT *mixer, const char *target) { + AlsaSndZoneT * zone = NULL; - assert(mixer->zones[0]); + if (mixer->nbZones == 0) { + AFB_ApiError(mixer->api, "%s mixer=%s does not have any zone", __func__, mixer->uid); + goto fail; + } // search for subdev into every registered loop - for (int idx = 0; mixer->zones[idx]; idx++) { - if (mixer->zones[idx]->uid && !strcasecmp(mixer->zones[idx]->uid, target)) { - return mixer->zones[idx]; - } + cds_list_for_each_entry(zone, &mixer->zones.list, list) { + if (zone->uid && !strcasecmp(zone->uid, target)) { + return zone; + } } - AFB_ApiError(mixer->api, "ApiZoneGetByUid mixer=%s fail to find zone=%s", mixer->uid, target); +fail: + AFB_ApiError(mixer->api, "%s mixer=%s fail to find zone=%s", __func__, mixer->uid, target); return NULL; } STATIC AlsaPcmChannelT* ProcessOneChannel(SoftMixerT *mixer, const char* uid, json_object *channelJ) { + AlsaPcmChannelT *channel = calloc(1, sizeof (AlsaPcmChannelT)); + if (!channel) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } + + CDS_INIT_LIST_HEAD(&channel->list); - int error = wrap_json_unpack(channelJ, "{ss,si !}" + channel->volume = -1; + + int error = wrap_json_unpack(channelJ, "{ss,si,s?f !}" , "target", &channel->uid , "channel", &channel->port + , "volume", &channel->volume ); - if (error) goto OnErrorExit; + if (error) + goto fail; channel->uid = strdup(channel->uid); + if (!channel->uid) { + SOFTMIXER_NOMEM(mixer->api); + goto fail_channel; + } + + AFB_ApiDebug(mixer->api, "%s: uid %s, channel uid %s, port %d, volume %f", + __func__, uid, channel->uid, channel->port, channel->volume); + return channel; -OnErrorExit: - AFB_ApiError(mixer->api, "ProcessOneChannel: zone=%s channel: missing (target|channel) json=%s", uid, json_object_get_string(channelJ)); +fail_channel: + free(channel); +fail: + AFB_ApiError(mixer->api, "%s: zone=%s channel: missing (target|channel) json=%s", __func__, uid, json_object_get_string(channelJ)); return NULL; } -STATIC AlsaSndZoneT *AttacheOneZone(SoftMixerT *mixer, const char *uid, json_object *zoneJ) { - AlsaSndZoneT *zone = calloc(1, sizeof (AlsaSndZoneT)); +STATIC AlsaSndZoneT *AttachOneZone(SoftMixerT *mixer, const char *uid, json_object *zoneJ) { + + AFB_ApiDebug(mixer->api, "%s uid %s", __func__, uid); + json_object *sinkJ = NULL, *sourceJ = NULL; size_t count; int error; + AlsaSndZoneT *zone = calloc(1, sizeof (AlsaSndZoneT)); + if (!zone) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } + + CDS_INIT_LIST_HEAD(&zone->list); + CDS_INIT_LIST_HEAD(&zone->sinks.list); + CDS_INIT_LIST_HEAD(&zone->sources.list); + error = wrap_json_unpack(zoneJ, "{ss,s?o,s?o !}" , "uid", &zone->uid , "sink", &sinkJ , "source", &sourceJ ); if (error || (!sinkJ && sourceJ)) { - AFB_ApiNotice(mixer->api, "AttacheOneZone missing 'uid|sink|source' error=%s zone=%s", wrap_json_get_error_string(error), json_object_get_string(zoneJ)); - goto OnErrorExit; + AFB_ApiNotice(mixer->api, "%s missing 'uid|sink|source' error=%s zone=%s", __func__, wrap_json_get_error_string(error), json_object_get_string(zoneJ)); + goto fail_zone; } // make sure remain valid even when json object is removed zone->uid = strdup(zone->uid); + if (!zone->uid) { + SOFTMIXER_NOMEM(mixer->api); + goto fail_zone; + } + + AlsaPcmChannelT * channel = NULL; if (sinkJ) { switch (json_object_get_type(sinkJ)) { case json_type_object: - zone->sinks = calloc(2, sizeof (void*)); - zone->sinks[0] = ProcessOneChannel(mixer, zone->uid, sinkJ); - if (!zone->sinks[0]) goto OnErrorExit; + + channel = ProcessOneChannel(mixer, zone->uid, sinkJ); + if (!channel) + goto fail_uid; + + zone->nbSinks++; + cds_list_add_tail(&channel->list, &zone->sinks.list); break; case json_type_array: count = json_object_array_length(sinkJ); - zone->sinks = calloc(count + 1, sizeof (void*)); + for (int idx = 0; idx < count; idx++) { json_object *subdevJ = json_object_array_get_idx(sinkJ, idx); - zone->sinks[idx] = ProcessOneChannel(mixer, zone->uid, subdevJ); - if (error) goto OnErrorExit; + + channel = ProcessOneChannel(mixer, zone->uid, subdevJ); + if (!channel) + goto fail_uid; + + zone->nbSinks++; + cds_list_add_tail(&channel->list, &zone->sinks.list); + } break; default: - AFB_ApiError(mixer->api, "AttacheOneZone: Mixer=%s Hal=%s zone=%s invalid mapping=%s", mixer->uid, uid, zone->uid, json_object_get_string(sinkJ)); - goto OnErrorExit; + AFB_ApiError(mixer->api, "%s: Mixer=%s Hal=%s zone=%s invalid mapping=%s", __func__, mixer->uid, uid, zone->uid, json_object_get_string(sinkJ)); + goto fail; } } if (sourceJ) { + switch (json_object_get_type(sourceJ)) { case json_type_object: - zone->sources = calloc(2, sizeof (void*)); - zone->sources[0] = ProcessOneChannel(mixer, zone->uid, sourceJ); - if (!zone->sources[0]) goto OnErrorExit; + channel = ProcessOneChannel(mixer, zone->uid, sourceJ); + if (!channel) + goto fail_uid; + zone->nbSources++; + cds_list_add(&channel->list, &zone->sources.list); break; case json_type_array: count = json_object_array_length(sourceJ); - zone->sources = calloc(count + 1, sizeof (void*)); + for (int idx = 0; idx < count; idx++) { json_object *subdevJ = json_object_array_get_idx(sourceJ, idx); - zone->sources[idx] = ProcessOneChannel(mixer, zone->uid, subdevJ); - if (error) goto OnErrorExit; + channel = ProcessOneChannel(mixer, zone->uid, subdevJ); + if (!zone) + goto fail_uid; + + zone->nbSources++; + cds_list_add(&channel->list, &zone->sources.list); } break; default: - AFB_ApiError(mixer->api, "AttacheOneZone:Mixer=%s Hal=%s zone=%s mapping=%s", mixer->uid, uid, zone->uid, json_object_get_string(sourceJ)); - goto OnErrorExit; + AFB_ApiError(mixer->api, "%s: Mixer=%s Hal=%s zone=%s mapping=%s", + __func__, mixer->uid, uid, zone->uid, json_object_get_string(sourceJ)); + goto fail; } } + AFB_ApiDebug(mixer->api, "%s uid %s DONE", __func__, uid); return zone; -OnErrorExit: +fail_uid: + free((char*)zone->uid); +fail_zone: + free(zone); +fail: return NULL; } -PUBLIC int ApiZoneAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ) { +static void zoneDestroy(SoftMixerT* mixer, void * arg) { + AlsaSndZoneT * zone = (AlsaSndZoneT*) arg; - int index; + AFB_ApiDebug(mixer->api, "%s... %s (%d sinks, %d sources)", __func__, zone->uid, zone->nbSinks, zone->nbSources); - for (index = 0; index < mixer->max.zones; index++) { - if (!mixer->zones[index]) break; - } + if (zone->routeConfig) { + AFB_ApiDebug(mixer->api, "%s... %s delete route config", __func__, zone->uid); + snd_config_delete(zone->routeConfig); + snd_config_update(); + zone->routeConfig = NULL; + } + + AlsaPcmChannelT * channel, *tmp; - if (index == mixer->max.zones) { - AFB_ReqFailF(request, "too-small", "mixer=%s max zone=%d", mixer->uid, mixer->max.zones); - goto OnErrorExit; + cds_list_for_each_entry_safe(channel, tmp, &zone->sinks.list, list) { + cds_list_del(&channel->list); + free(channel); + } + + cds_list_del(&zone->list); + mixer->nbZones--; + + free((char*) zone->uid); + free(zone); + AFB_ApiDebug(mixer->api, "%s... DONE !", __func__); +} + +PUBLIC AlsaSndZoneT * zoneCreate(SoftMixerT* mixer, const char * uid, json_object * argsJ) { + + AlsaSndZoneT * zone = AttachOneZone(mixer, uid, argsJ); + if (!zone) + goto fail; + + // must be set now; AlsaCreateRoute needs it ! + mixer->nbZones++; + cds_list_add_tail(&zone->list, &mixer->zones.list); + AlsaMixerTransactionObjectAdd(mixer->transaction, zone, zoneDestroy); + + AlsaPcmCtlT *routeConfig = AlsaCreateRoute(mixer, zone, 0); + if (!routeConfig) { + AFB_ApiError(mixer->api, + "%s: Mixer=%s Hal=%s zone=%s Fail to attach PCM Route", + __func__, mixer->uid, uid, zone->uid); + goto fail; } + zone->isPcmPlug = routeConfig->isPcmPlug; + +fail: + return zone; +} + +PUBLIC int ApiZoneAttach(SoftMixerT *mixer, AFB_ReqT request, const char * uid, json_object * argsJ) { + + AFB_ApiDebug(mixer->api, "%s uid %s", __func__, uid); + + if (mixer->nbZones >= mixer->max.zones) { + AFB_ReqFailF(request, "too-small", "mixer=%s max zone=%d", mixer->uid, mixer->max.zones); + goto OnErrorExit; + } + switch (json_object_get_type(argsJ)) { - long count; + long count; case json_type_object: { - AlsaSndZoneT * zone = AttacheOneZone(mixer, uid, argsJ); - if (!zone) { + AlsaSndZoneT * newZone = zoneCreate(mixer, uid, argsJ); + if (!newZone) { AFB_ReqFailF(request, "bad-zone", "mixer=%s invalid zone= %s", mixer->uid, json_object_get_string(argsJ)); goto OnErrorExit; } - - // must be set now; AlsaCreateRoute needs it ! - mixer->zones[index] = zone; - AlsaPcmCtlT *routeConfig = AlsaCreateRoute(mixer, zone, 0); - if (!routeConfig) { - AFB_ApiError(mixer->api, - "%s: Mixer=%s Hal=%s zone=%s Fail to attach PCM Route", - __func__, mixer->uid, uid, zone->uid); - goto OnErrorExit; - } break; } case json_type_array: count = json_object_array_length(argsJ); - if (count > (mixer->max.zones - index)) { + if (count > (mixer->max.zones - mixer->nbZones)) { AFB_ReqFailF(request, "too-small", "mixer=%s max zone=%d", mixer->uid, mixer->max.zones); goto OnErrorExit; } for (int idx = 0; idx < count; idx++) { json_object *zoneJ = json_object_array_get_idx(argsJ, idx); - AlsaSndZoneT * zone = AttacheOneZone(mixer, uid, zoneJ); - if (!zone) { + AlsaSndZoneT * newZone = zoneCreate(mixer, uid, zoneJ); + if (!newZone) { AFB_ReqFailF(request, "bad-zone", "mixer=%s invalid zone= %s", mixer->uid, json_object_get_string(zoneJ)); goto OnErrorExit; } - - mixer->zones[index + idx] = zone; - AlsaPcmCtlT *routeConfig = AlsaCreateRoute(mixer, zone, 0); - if (!routeConfig) { - AFB_ApiError(mixer->api, - "%s: Mixer=%s Hal=%s zone=%s Fail to attach PCM Route", - __func__, mixer->uid, uid, zone->uid); - goto OnErrorExit; - } - } break; default: @@ -192,6 +287,7 @@ PUBLIC int ApiZoneAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, j goto OnErrorExit; } + AFB_ApiDebug(mixer->api, "%s uid %s DONE", __func__, uid); return 0; OnErrorExit: diff --git a/plugins/alsa/alsa-bluez.c b/plugins/alsa/alsa-bluez.c deleted file mode 100644 index 080c3c8..0000000 --- a/plugins/alsa/alsa-bluez.c +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright(C) 2018 "IoT.bzh" - * Author Thierry Bultel - * - * 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. - * - * - */ - - -#include "alsa-bluez.h" - -#include -#include - -#define ALSA_BLUEZ_PROXY_LIB "/usr/lib/alsa-lib/libasound_module_pcm_bluealsa_proxy.so" -#define ALSA_BLUEZ_PROXY_SETDEVICE "bluealsa_proxy_set_remote_device" - -typedef int (*bluealsa_set_remote_device_ptr) (const char * interface, const char * device, const char * profile); - -static bluealsa_set_remote_device_ptr bluealsa_proxy_set_remote_device = NULL; - -void alsa_bluez_init() { - static bool initialized = false; - if (initialized) - goto failed; - -#if SND_LIB_VERSION >= (1<<16|1<<8|6) - char errbuf[256]; - void * dl = snd_dlopen(ALSA_BLUEZ_PROXY_LIB, RTLD_NOW, errbuf, 256); -#else - void * dl = snd_dlopen(ALSA_BLUEZ_PROXY_LIB, RTLD_NOW); -#endif - if (!dl) { - printf("Failed to open bluealsa proxy plugin\n"); - goto failed; - } - - void * func = snd_dlsym(dl, ALSA_BLUEZ_PROXY_SETDEVICE, SND_DLSYM_VERSION(SND_PCM_DLSYM_VERSION)); - if (!func) { - printf("Unable to find %s symbol\n", ALSA_BLUEZ_PROXY_SETDEVICE); - goto failed; - } - - bluealsa_proxy_set_remote_device = func; - initialized = true; - -failed: - return; -} - -int alsa_bluez_set_remote_device(const char * interface, const char * device, const char * profile) { - if (!bluealsa_proxy_set_remote_device) - return -1; - - return bluealsa_proxy_set_remote_device(interface,device,profile); -} - - diff --git a/plugins/alsa/alsa-bluez.h b/plugins/alsa/alsa-bluez.h deleted file mode 100644 index c288d38..0000000 --- a/plugins/alsa/alsa-bluez.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright(C) 2018 "IoT.bzh" - * Author Thierry Bultel - * - * 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. - * - */ - -#ifndef __INC_ALSA_BLUEZ_H -#define __INC_ALSA_BLUEZ_H - -#include - -extern void alsa_bluez_init(); -extern int alsa_bluez_set_remote_device(const char * interface, const char * device, const char * profile); - -#endif /* __INC_ALSA_BLUEZ_H */ diff --git a/plugins/alsa/alsa-core-ctl.c b/plugins/alsa/alsa-core-ctl.c index 7a0c476..dcfd860 100644 --- a/plugins/alsa/alsa-core-ctl.c +++ b/plugins/alsa/alsa-core-ctl.c @@ -25,12 +25,11 @@ for the specific language governing permissions and #define _GNU_SOURCE // needed for vasprintf #include "alsa-softmixer.h" -#include "alsa-bluez.h" #include #include -typedef struct { +typedef struct SubscribeHandleT_ { SoftMixerT *mixer; sd_event_source* evtsrc; const char* uid; @@ -151,24 +150,6 @@ OnErrorExit: return NULL; } -PUBLIC snd_ctl_t *AlsaCtlOpenCtl(SoftMixerT *mixer, const char *cardid) { - int error; - snd_ctl_t *ctl; - - if (!cardid) goto OnErrorExit; - - if ((error = snd_ctl_open(&ctl, cardid, SND_CTL_READONLY)) < 0) { - cardid = "Not Defined"; - goto OnErrorExit; - } - - return ctl; - -OnErrorExit: - AFB_ApiError(mixer->api, "AlsaCtlOpenCtl: fail to find sndcard by id= %s", cardid); - return NULL; -} - STATIC void CtlElemIdDisplay(SoftMixerT *mixer, snd_ctl_elem_info_t *elemInfo, snd_ctl_elem_value_t *elemData) { @@ -275,7 +256,7 @@ PUBLIC int CtlElemIdSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, snd_ctl_ele OnErrorExit: numid = snd_ctl_elem_info_get_numid(elemInfo); name = snd_ctl_elem_info_get_name(elemInfo); - AFB_ApiError(mixer->api, "CtlElemIdSetInt: numid=%d name=%s not writable", numid, name); + AFB_ApiError(mixer->api, "%s: numid=%d name=%s not writable", __func__, numid, name); return -1; } @@ -290,26 +271,42 @@ PUBLIC snd_ctl_card_info_t *AlsaCtlGetCardInfo(SoftMixerT *mixer, const char *ca /* "bluealsa" is the name of the control external plugin * (https://www.alsa-project.org/alsa-doc/alsa-lib/ctl_external_plugins.html) */ + if (strstr(cardid, "bluealsa")) { - cardid="bluealsa"; - alsa_bluez_init(); + cardid = strdup("bluealsa"); + if (cardid == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } } AFB_ApiNotice(mixer->api, "Opening card control '%s'", cardid); if ((error = snd_ctl_open(&ctl, cardid, SND_CTL_READONLY)) < 0) { cardid = "Not Defined"; - goto OnErrorExit; + goto fail; } snd_ctl_card_info_t *cardInfo = malloc(snd_ctl_card_info_sizeof()); + if (cardInfo == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto fail_ctl; + } + if ((error = snd_ctl_card_info(ctl, cardInfo)) < 0) { - goto OnErrorExit; + AFB_ApiError(mixer->api, "%s fail to find sndcard by id= %s", __func__, cardid); + goto fail_card_info; } + + snd_ctl_close(ctl); return cardInfo; -OnErrorExit: - AFB_ApiError(mixer->api, "AlsaCtlGetInfo: fail to find sndcard by id= %s", cardid); +fail_card_info: + free(cardInfo); +fail_ctl: + snd_ctl_close(ctl); +fail: + AFB_ApiError(mixer->api, "%s: fail to find sndcard by id= %s", __func__, cardid); return NULL; } @@ -495,50 +492,76 @@ STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, v snd_ctl_event_t *eventId; snd_ctl_elem_id_t *elemId; long value; - int index; + + AFB_ApiDebug(mixer->api, "%s event on card %s", __func__, sndcard->cid.cardid); if ((revents & EPOLLHUP) != 0) { AFB_ApiNotice(mixer->api, "%s hanghup [card:%s disconnected]", __func__, sHandle->uid); goto OnSuccessExit; } - if ((revents & EPOLLIN) == 0) goto OnSuccessExit; + if ((revents & EPOLLIN) == 0) { + AFB_ApiNotice(mixer->api, "%s: no events", __func__); + goto OnSuccessExit; + } // initialise event structure on stack snd_ctl_event_alloca(&eventId); snd_ctl_elem_id_alloca(&elemId); error = snd_ctl_read(sndcard->ctl, eventId); - if (error < 0) goto OnErrorExit; + if (error < 0) { + AFB_ApiDebug(mixer->api, "%s: failed to read control", __func__); + goto OnErrorExit; + } // we only process sndctrl element - if (snd_ctl_event_get_type(eventId) != SND_CTL_EVENT_ELEM) goto OnSuccessExit; + if (snd_ctl_event_get_type(eventId) != SND_CTL_EVENT_ELEM) { + AFB_ApiDebug(mixer->api, "%s: bad event type", __func__); + goto OnSuccessExit; + } // we only process value changed events unsigned int eventMask = snd_ctl_event_elem_get_mask(eventId); - if (!(eventMask & SND_CTL_EVENT_MASK_VALUE)) goto OnSuccessExit; + if (!(eventMask & SND_CTL_EVENT_MASK_VALUE)) { + goto OnSuccessExit; + } // extract element from event and get value snd_ctl_event_elem_get_id(eventId, elemId); error = CtlElemIdGetLong(mixer, sHandle->sndcard, elemId, &value); - if (error) goto OnErrorExit; + if (error) { + AFB_ApiDebug(mixer->api, "%s: failed to get elem id", __func__); + goto OnErrorExit; + } // get numdid and name from elemId snd_ctl_elem_info_t *elemInfo; snd_ctl_elem_info_alloca(&elemInfo); snd_ctl_elem_info_set_id(elemInfo, elemId); - if (snd_ctl_elem_info(sndcard->ctl, elemInfo) < 0) goto OnErrorExit; + if (snd_ctl_elem_info(sndcard->ctl, elemInfo) < 0) { + AFB_ApiDebug(mixer->api, "%s: failed to get elem info", __func__); + goto OnErrorExit; + } int numid = snd_ctl_elem_info_get_numid(elemInfo); + + AFB_ApiDebug(mixer->api, "%s: event on elem id %d", __func__, numid); + const char *name= snd_ctl_elem_info_get_name(elemInfo); - for (index = 0; sndcard->registry[index]; index++) { - RegistryEntryPcmT * reg = sndcard->registry[index]; - snd_pcm_t * pcm = reg->pcm->handle; - if (reg->numid == numid) { + RegistryEntryPcmT * registry = NULL; + + bool found = false; + + cds_list_for_each_entry(registry, &sndcard->registryList, cardListEntry) { + snd_pcm_t * pcm = registry->pcm->handle; + + if (registry->numid == numid) { + found = true; int ret; - switch (reg->type) { + switch (registry->type) { case FONTEND_NUMID_RUN: - AlsaPcmCopyMuteSignal(mixer, reg->pcm, !value); + AlsaPcmCopyMuteSignal(mixer, registry->pcm, !value); ret = snd_pcm_pause(pcm, (int) (!value)); AFB_ApiNotice(mixer->api, "%s:%s numid=%d name=%s active=%ld ret %d", __func__, sHandle->uid, numid, name, value, ret); @@ -548,8 +571,8 @@ STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, v break; case FONTEND_NUMID_PAUSE: - AlsaPcmCopyMuteSignal(mixer, reg->pcm, value); - ret = snd_pcm_pause(reg->pcm->handle, (int) value); + AlsaPcmCopyMuteSignal(mixer, registry->pcm, value); + ret = snd_pcm_pause(registry->pcm->handle, (int) value); AFB_ApiNotice(mixer->api, "%s:%s numid=%d name=%s pause=%ld ret %d", __func__, sHandle->uid, numid, name, value, ret); if (ret < 0) { @@ -558,53 +581,51 @@ STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, v break; case FONTEND_NUMID_IGNORE: default: - AFB_ApiInfo(mixer->api, + AFB_ApiDebug(mixer->api, "%s:%s numid=%d name=%s ignored=%ld", __func__, sHandle->uid, numid, name, value); } break; } } - if (index == sndcard->rcount) { - AFB_ApiNotice(mixer->api, "%s:%s numid=%d (unknown)", __func__, sHandle->uid, numid); + if (!found) { + AFB_ApiNotice(mixer->api, "%s:%s numid=%d is unknown", __func__, sHandle->uid, numid); } OnSuccessExit: return 0; OnErrorExit: - AFB_ApiInfo(mixer->api, "%s: ignored unsupported event", __func__); + AFB_ApiDebug(mixer->api, "%s: ignored unsupported event", __func__); return 0; } -PUBLIC snd_ctl_t* AlsaCrlFromPcm(SoftMixerT *mixer, snd_pcm_t *pcm) { - char buffer[32]; - int error; - snd_ctl_t *ctl; - snd_pcm_info_t *pcmInfo; - snd_pcm_info_alloca(&pcmInfo); - if ((error = snd_pcm_info(pcm, pcmInfo)) < 0) goto OnErrorExit; +PUBLIC int AlsaCtlUnSubscribe(SoftMixerT *mixer, AlsaSndCtlT *sndcard) { + SubscribeHandleT * handle = sndcard->eventSubscribeHandle; - int pcmCard = snd_pcm_info_get_card(pcmInfo); - snprintf(buffer, sizeof (buffer), "hw:%i", pcmCard); - if ((error = snd_ctl_open(&ctl, buffer, SND_CTL_READONLY)) < 0) goto OnErrorExit; + AFB_ApiDebug(mixer->api,"%s for card %s", __func__, sndcard->cid.cardid); - return ctl; - -OnErrorExit: - return NULL; + sd_event_source_unref(handle->evtsrc); + free(handle); + return 0; } PUBLIC int AlsaCtlSubscribe(SoftMixerT *mixer, const char *uid, AlsaSndCtlT *sndcard) { int error; char string [32]; struct pollfd pfds; - SubscribeHandleT *handle = malloc(sizeof (SubscribeHandleT)); + + SubscribeHandleT *handle = calloc(1, sizeof (SubscribeHandleT)); + if (handle == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto OnErrorExit; + } handle->mixer = mixer; handle->sndcard = sndcard; handle->uid = uid; + sndcard->eventSubscribeHandle = handle; // subscribe for sndctl events attached to cardid if ((error = snd_ctl_subscribe_events(handle->sndcard->ctl, 1)) < 0) { @@ -636,36 +657,63 @@ OnErrorExit: return -1; } -PUBLIC int AlsaCtlRegister(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaPcmCtlT *pcmdev, RegistryNumidT type, int numid) { - int index; - AFB_ApiInfo(mixer->api,"%s: registered ID %d.", __func__, numid); +PUBLIC void AlsaCtlUnregister(SoftMixerT* mixer, void * arg) { - for (index = 0; index < sndcard->rcount; index++) { - if (!sndcard->registry[index]) break; - } + RegistryEntryPcmT * registryEntry = (RegistryEntryPcmT*) arg; + + AFB_ApiDebug(mixer->api,"%s: card %s : Unregistering control ID %d (%ld in registry).", + __func__, registryEntry->sndcard->cid.cardid, registryEntry->numid, registryEntry->sndcard->nbRegistry); + AlsaSndCtlT * sndcard = registryEntry->sndcard; + + cds_list_del(®istryEntry->cardListEntry); + sndcard->nbRegistry--; - if (index == sndcard->rcount) { + if (sndcard->nbRegistry <= 0) + AlsaCtlUnSubscribe(mixer, sndcard); + + free(registryEntry); + AFB_ApiDebug(mixer->api,"%s: DONE", __func__); +} + + +PUBLIC int AlsaCtlRegister(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaPcmCtlT *pcmdev, RegistryNumidT type, int numid) { + + AFB_ApiDebug(mixer->api,"%s: registering control ID %d to %s", __func__, numid, sndcard->cid.cardid); + + if (sndcard->nbRegistry >= SMIXER_SUBDS_CTLS ) { AFB_ApiError(mixer->api, - "%s cardid=%s cardname=%s to many audio stream max=%ld", - __func__, sndcard->cid.cardid, sndcard->cid.longname, sndcard->rcount); - goto OnErrorExit; + "%s cardid='%s' cardname='%s' too many registered controls max=%ld", + __func__, sndcard->cid.cardid, sndcard->cid.longname, sndcard->nbRegistry); + goto fail; } // If 1st registration then register to card event - if (index == 0) { + if (sndcard->nbRegistry == 0) { AlsaCtlSubscribe(mixer, sndcard->cid.cardid, sndcard); } // store PCM in order to pause/resume depending on event RegistryEntryPcmT *entry = calloc(1, sizeof (RegistryEntryPcmT)); - sndcard->registry[index] = entry; + if (entry == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } + CDS_INIT_LIST_HEAD(&entry->cardListEntry); + entry->pcm = pcmdev; entry->numid = numid; entry->type = type; + entry->sndcard = sndcard; + + sndcard->nbRegistry++; + + AlsaMixerTransactionObjectAdd(mixer->transaction, entry, AlsaCtlUnregister); + + cds_list_add_tail(&entry->cardListEntry, &sndcard->registryList); return 0; -OnErrorExit: +fail: return -1; } diff --git a/plugins/alsa/alsa-core-pcm.c b/plugins/alsa/alsa-core-pcm.c index 25872ae..adceebc 100644 --- a/plugins/alsa/alsa-core-pcm.c +++ b/plugins/alsa/alsa-core-pcm.c @@ -34,6 +34,16 @@ for the specific language governing permissions and static int xrun(snd_pcm_t * pcm, int error); static int suspend(snd_pcm_t * pcm, int error); +typedef enum { + PCM_COPY_MUTE, + PCM_COPY_UNMUTE, + PCM_COPY_END, + PCM_COPY_LAST // Do not put anything after +} PcmCopyEventType; + +typedef struct { + PcmCopyEventType eventType; +} PcmCopyEvent; STATIC int AlsaPeriodSize(snd_pcm_format_t pcmFormat) { int pcmSampleSize; @@ -81,12 +91,13 @@ PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) { snd_pcm_access_t access; AlsaPcmHwInfoT * opts = pcm->params; + const char * card = pcm->cid.cardid; const char * modeS = mode==SND_PCM_STREAM_PLAYBACK?"PLAYBACK":"CAPTURE"; - AFB_ApiInfo(mixer->api, - "%s: mixer info %s uid %s , pcm %s, mode %s", - __func__, mixer->info, mixer->uid, pcm->cid.cardid, modeS); + AFB_ApiDebug(mixer->api, + "%s: mixer info %s uid %s, pcm %s, mode %s", + __func__, mixer->info, mixer->uid, card, modeS); // retrieve hardware config from PCM snd_pcm_hw_params_alloca(&pxmHwParams); @@ -96,7 +107,7 @@ PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) { goto OnErrorExit; } - AFB_ApiDebug(mixer->api, "PARAMS before:\n"); + AFB_ApiDebug(mixer->api, "(%s): PARAMS before:", card); AlsaDumpPcmParams(mixer, pxmHwParams); if (!opts->access) @@ -106,8 +117,8 @@ PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) { error = snd_pcm_hw_params_set_access(pcm->handle, pxmHwParams, opts->access); if (error) { AFB_ApiError(mixer->api, - "%s set_access failed (ignore this error): mixer=%s cardid=%s access=%d Fail current=%d mode error=%s", - __func__, mixer->uid, pcm->cid.cardid, opts->access, access, snd_strerror(error)); + "%s (%s) set_access failed (ignore this error): mixer=%s access=%d Fail current=%d mode error=%s", + __func__, card, mixer->uid, opts->access, access, snd_strerror(error)); //Fulup goto OnErrorExit; }; @@ -115,8 +126,8 @@ PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) { snd_pcm_hw_params_get_format(pxmHwParams, &format); if ((error = snd_pcm_hw_params_set_format(pcm->handle, pxmHwParams, opts->format)) < 0) { AFB_ApiError(mixer->api, - "%s: mixer=%s cardid=%s Set_Format=%s (%d) FAILED current=%d error=%s", - __func__, mixer->uid, pcm->cid.cardid, opts->formatS, opts->format, format, snd_strerror(error)); + "%s (%s) mixer=%s Set_Format=%s (%d) FAILED current=%d error=%s", + __func__, card, mixer->uid, opts->formatS, opts->format, format, snd_strerror(error)); AlsaDumpFormats(mixer, pcm->handle); goto OnErrorExit; } @@ -124,22 +135,22 @@ PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) { if (opts->rate > 0 ) { - AFB_ApiInfo(mixer->api," %s: set rate to %d", __func__, opts->rate); + AFB_ApiDebug(mixer->api,"%s (%s): set rate to %d", __func__, card, opts->rate); unsigned int pcmRate = opts->rate; /* Attempt to set the rate. Failing on a capture dev is acceptable */ error = snd_pcm_hw_params_set_rate_near(pcm->handle, pxmHwParams, &opts->rate, 0); if ( mode == SND_PCM_STREAM_PLAYBACK && error < 0) { AFB_ApiError(mixer->api, - "%s: mixer=%s cardid=%s FailSet_Rate=%d error=%s", - __func__, mixer->uid, pcm->cid.cardid, opts->rate, snd_strerror(error)); + "%s (%s): mixer=%s FailSet_Rate=%d error=%s", + __func__, card, mixer->uid, opts->rate, snd_strerror(error)); goto OnErrorExit; } // check we got requested rate if (mode == SND_PCM_STREAM_PLAYBACK && opts->rate != pcmRate) { AFB_ApiError(mixer->api, - "%s: mixer=%s cardid=%s Set_Rate Fail ask=%dHz get=%dHz", - __func__, mixer->uid, pcm->cid.cardid,pcmRate, opts->rate); + "%s (%s): mixer=%s Set_Rate Fail ask=%dHz get=%dHz", + __func__, card, mixer->uid, pcmRate, opts->rate); goto OnErrorExit; } } @@ -147,8 +158,8 @@ PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) { if (opts->channels) { if ((error = snd_pcm_hw_params_set_channels(pcm->handle, pxmHwParams, opts->channels)) < 0) { AFB_ApiError(mixer->api, - "%s: mixer=%s cardid=%s Set_Channels=%d Fail error=%s", - __func__, mixer->uid, pcm->cid.cardid, opts->channels, snd_strerror(error)); + "%s (%s): mixer=%s Set_Channels=%d Fail error=%s", + __func__, card, mixer->uid, opts->channels, snd_strerror(error)); goto OnErrorExit; }; } @@ -165,7 +176,7 @@ PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) { error = snd_pcm_hw_params_get_buffer_time_max(pxmHwParams, &buffer_time, 0); - AFB_ApiInfo(mixer->api, "HW_BUFFER_TIME MAX is %d\n", buffer_time); + AFB_ApiDebug(mixer->api, "(%s) HW_BUFFER_TIME MAX is %d", card, buffer_time); if (buffer_time > 500000) buffer_time = 500000; @@ -178,45 +189,45 @@ PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) { } if (period_time > 0) { - AFB_ApiInfo(mixer->api, "SET PERIOD TIME to %d", period_time); + AFB_ApiDebug(mixer->api, "(%s) SET PERIOD TIME to %d", card, period_time); error = snd_pcm_hw_params_set_period_time_near(pcm->handle, pxmHwParams, &period_time, 0); } else { - AFB_ApiInfo(mixer->api, "SET PERIOD SIZE..."); + AFB_ApiDebug(mixer->api, "(%s) SET PERIOD SIZE...", card); error = snd_pcm_hw_params_set_period_size_near(pcm->handle, pxmHwParams, &period_frames, 0); } if (error < 0) { AFB_ApiError(mixer->api, - "%s: mixer=%s cardid=%s Fail to set period in hwparams error=%s", - __func__, mixer->uid, pcm->cid.cardid, snd_strerror(error)); + "%s (%s): mixer=%s Fail to set period in hwparams error=%s", + __func__, card, mixer->uid, snd_strerror(error)); goto OnErrorExit; } if (buffer_time > 0) { - AFB_ApiInfo(mixer->api, "SET BUFFER TIME to %d", buffer_time); + AFB_ApiDebug(mixer->api, "(%s) SET BUFFER TIME to %d", card, buffer_time); error = snd_pcm_hw_params_set_buffer_time_near(pcm->handle, pxmHwParams, &buffer_time, 0); } else { - AFB_ApiInfo(mixer->api, "SET BUFFER SIZE..."); + AFB_ApiDebug(mixer->api, "(%s) SET BUFFER SIZE to %ld...", card, buffer_frames); error = snd_pcm_hw_params_set_buffer_size_near(pcm->handle, pxmHwParams, &buffer_frames); } if (error < 0) { AFB_ApiError(mixer->api, - "%s: mixer=%s cardid=%s Fail to set buffer in hwparams error=%s", - __func__, mixer->uid, pcm->cid.cardid, snd_strerror(error)); + "%s (%s): mixer=%s Fail to set buffer in hwparams error=%s", + __func__, card, mixer->uid, snd_strerror(error)); goto OnErrorExit; } // store selected values if ((error = snd_pcm_hw_params(pcm->handle, pxmHwParams)) < 0) { AFB_ApiError(mixer->api, - "%s: mixer=%s cardid=%s Fail to apply hwparams error=%s", - __func__, mixer->uid, pcm->cid.cardid, snd_strerror(error)); + "%s (%s): mixer=%s Fail to apply hwparams error=%s", + __func__, card, mixer->uid, snd_strerror(error)); goto OnErrorExit; } - AFB_ApiDebug(mixer->api, "PARAMS after:\n"); + AFB_ApiDebug(mixer->api, "(%s) PARAMS after:", card); AlsaDumpPcmParams(mixer, pxmHwParams); // check we effective hw params after optional format change @@ -224,13 +235,13 @@ PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) { snd_pcm_hw_params_get_format(pxmHwParams, &opts->format); snd_pcm_hw_params_get_rate(pxmHwParams, &opts->rate, 0); - AFB_ApiInfo(mixer->api, "rate is %d", opts->rate); + AFB_ApiDebug(mixer->api, "(%s) rate is %d", card, opts->rate); opts->sampleSize = AlsaPeriodSize(opts->format); if (opts->sampleSize == 0) { AFB_ApiError(mixer->api, - "%s: mixer=%s cardid=%s Fail unsupported format format=%d", - __func__, mixer->uid, pcm->cid.cardid, opts->format); + "%s (%s): mixer=%s Fail unsupported format format=%d", + __func__, card, mixer->uid, opts->format); goto OnErrorExit; } @@ -240,8 +251,8 @@ PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) { snd_pcm_hw_params_get_buffer_size(pxmHwParams, &buffer_size); if (chunk_size == buffer_size) { AFB_ApiError(mixer->api, - "Can't use period equal to buffer size (%lu == %lu)", - chunk_size, buffer_size); + "(%s) Can't use period equal to buffer size (%lu == %lu)", + card, chunk_size, buffer_size); goto OnErrorExit; } @@ -263,8 +274,8 @@ PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) { if ((error = snd_pcm_sw_params_set_avail_min(pcm->handle, pxmSwParams, n)) < 0) { AFB_ApiError(mixer->api, - "%s: mixer=%s cardid=%s Fail set_buffersize error=%s", - __func__, mixer->uid, pcm->cid.cardid, snd_strerror(error)); + "%s (%s): mixer=%s Fail set_buffersize error=%s", + __func__, card, mixer->uid, snd_strerror(error)); goto OnErrorExit; }; @@ -285,39 +296,39 @@ PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) { if (start_threshold > n/2) start_threshold = n/2; - AFB_ApiInfo(mixer->api, "CALCULATED START THRESHOLD: %ld", start_threshold); + AFB_ApiDebug(mixer->api, "(%s) CALCULATED START THRESHOLD: %ld", card, start_threshold); if (mode == SND_PCM_STREAM_PLAYBACK) { start_threshold = 1; } - AFB_ApiInfo(mixer->api, "%s: Set start threshold to %ld", modeS, start_threshold); + AFB_ApiDebug(mixer->api, "(%s) %s: Set start threshold to %ld", card, modeS, start_threshold); error = snd_pcm_sw_params_set_start_threshold(pcm->handle, pxmSwParams, start_threshold); if (error < 0) { AFB_ApiError(mixer->api, - "%s: mixer=%s cardid=%s failed set start_threshold, error=%s", - __func__, mixer->uid, pcm->cid.cardid, snd_strerror(error)); + "%s (%s): mixer=%s failed set start_threshold, error=%s", + __func__, card, mixer->uid, snd_strerror(error)); goto OnErrorExit; } // push software params into PCM if ((error = snd_pcm_sw_params(pcm->handle, pxmSwParams)) < 0) { AFB_ApiError(mixer->api, - "%s: mixer=%s cardid=%s Fail to push SW params error=%s", - __func__, mixer->uid, pcm->cid.cardid, snd_strerror(error)); + "%s (%s): mixer=%s Fail to push SW params error=%s", + __func__, card, mixer->uid, snd_strerror(error)); goto OnErrorExit; }; AFB_ApiNotice(mixer->api, - "%s: mixer=%s cardid=%s Done channels=%d rate=%d format=%d access=%d ... done !", - __func__, mixer->uid, pcm->cid.cardid, opts->channels, opts->rate, opts->format, opts->access); + "%s (%s): mixer=%s Done channels=%d rate=%d format=%d access=%d ... DONE !", + __func__, card, mixer->uid, opts->channels, opts->rate, opts->format, opts->access); return 0; OnErrorExit: return -1; } -STATIC int AlsaPcmReadCB( struct pollfd * pfd, AlsaPcmCopyHandleT * pcmCopyHandle) { +STATIC int AlsaPcmReadCB( AlsaPcmCopyHandleT * pcmCopyHandle) { char string[32]; snd_pcm_sframes_t availIn; @@ -327,19 +338,6 @@ STATIC int AlsaPcmReadCB( struct pollfd * pfd, AlsaPcmCopyHandleT * pcmCopyHandl int err; - // PCM has was closed - if ((pfd->revents & POLLHUP) != 0) { - AFB_ApiNotice(pcmCopyHandle->api, - "%s PCM=%s hanghup/disconnected", - __func__, ALSA_PCM_UID(pcmIn, string)); - goto ExitOnSuccess; - } - - // ignore any non input events. This is not supposed to happen ever - if ((pfd->revents & EPOLLIN) == 0) { - goto ExitOnSuccess; - } - // do we have waiting frames ? availIn = snd_pcm_avail_update(pcmIn); if (availIn <= 0) { @@ -465,16 +463,20 @@ static void *readThreadEntry(void *handle) { AlsaPcmCopyHandleT *pcmCopyHandle = (AlsaPcmCopyHandleT*) handle; pcmCopyHandle->tid = (int) syscall(SYS_gettid); + int ix; AFB_ApiNotice(pcmCopyHandle->api, "%s :%s/%d Started, muted=%d", __func__, pcmCopyHandle->info, pcmCopyHandle->tid, pcmCopyHandle->pcmIn->mute); - struct pollfd * mutePfd = &pcmCopyHandle->pollFds[0]; - struct pollfd * framePfd = &pcmCopyHandle->pollFds[1]; + struct pollfd * eventFd = &pcmCopyHandle->pollFds[0]; + struct pollfd * framePfds = &pcmCopyHandle->pollFds[1]; - mutePfd->events = POLLIN | POLLHUP; - framePfd->events = POLLIN | POLLHUP; + eventFd->events = POLLIN | POLLHUP; + + for (ix = 0; ix nbPcmFds-1; ix++) { + framePfds[ix].events = POLLIN | POLLHUP; + } bool muted = pcmCopyHandle->pcmIn->mute; @@ -492,48 +494,60 @@ static void *readThreadEntry(void *handle) { if (err == 0) { /* timeout */ - AFB_ApiDebug(pcmCopyHandle->api, "%s(%s) alive, mute %d", __func__, pcmCopyHandle->pcmIn->cid.cardid, muted ); +// AFB_ApiDebug(pcmCopyHandle->api, "%s(%s) alive, mute %d", __func__, pcmCopyHandle->pcmIn->cid.cardid, muted ); continue; } - // handle the un/mute order - if ((mutePfd->revents & EPOLLIN) != 0) { - bool mute; - - size_t ret = read(mutePfd->fd, &mute, sizeof(mute)); - if (ret <= 0) - continue; + // handle the incoming events/mute order + if ((eventFd->revents & EPOLLIN) != 0) { + PcmCopyEvent event; - if (mute == muted) - continue; - - muted = mute; + size_t ret = read(eventFd->fd, &event, sizeof(event)); + if (ret <= 0) + continue; - if (muted) { - readSuspend(pcmCopyHandle); - } else { - readResume(pcmCopyHandle); + switch (event.eventType) { + case PCM_COPY_MUTE: + if (!muted) { + readSuspend(pcmCopyHandle); + muted = true; + } + break; + case PCM_COPY_UNMUTE: + if (muted) { + readResume(pcmCopyHandle); + muted = false; + }; + break; + case PCM_COPY_END: + AFB_ApiDebug(pcmCopyHandle->api, "%s ending -> EXIT", __func__); + goto done; + break; + case PCM_COPY_LAST: + default: + AFB_ApiError(pcmCopyHandle->api, "%s: Unexpected event 0x%x", __func__, event.eventType); + break; } continue; } unsigned short revents; - int ret = snd_pcm_poll_descriptors_revents(pcmCopyHandle->pcmIn->handle, &pcmCopyHandle->pollFds[1], 1, &revents); + int res = snd_pcm_poll_descriptors_revents(pcmCopyHandle->pcmIn->handle, framePfds, pcmCopyHandle->nbPcmFds, &revents); - if (ret == -ENODEV) { + if (res == -ENODEV) { sleep(1); continue; } - if (framePfd->revents & POLLHUP) { + if (revents & POLLHUP) { AFB_ApiNotice(pcmCopyHandle->api, "Frame POLLHUP"); continue; } - AlsaPcmReadCB(&pcmCopyHandle->pollFds[1], pcmCopyHandle); + AlsaPcmReadCB(pcmCopyHandle); } - +done: pthread_exit(0); return NULL; } @@ -554,6 +568,8 @@ static void *writeThreadEntry(void *handle) { snd_pcm_status(pcmOut, pcmOutStatus); pcmOutSize = snd_pcm_status_get_avail_max(pcmOutStatus); + const char * cardid = pcmCopyHandle->pcmOut->cid.cardid; + /* This threshold is the expected space available in the hw output buffer * The aim is to wait to have a significant amount of space, in order to * avoid to write to the device too often, or take a very small amount of @@ -566,17 +582,23 @@ static void *writeThreadEntry(void *handle) { sem_wait(&pcmCopyHandle->sem); while (true) { + + if (pcmCopyHandle->ending) { + AFB_ApiDebug(pcmCopyHandle->api, "%s: ending -> EXIT", __func__); + goto done; + } + snd_pcm_sframes_t used, nbWritten; snd_pcm_sframes_t availOut = snd_pcm_avail(pcmOut); if (availOut < 0) { if (availOut == -EPIPE) { - AFB_ApiDebug(pcmCopyHandle->api, "write update EPIPE"); + AFB_ApiDebug(pcmCopyHandle->api, "%s: write update EPIPE", cardid); xrun(pcmOut, (int)availOut); continue; } if (availOut == -ESTRPIPE) { - AFB_ApiDebug(pcmCopyHandle->api, "write update ESTRPIPE"); + AFB_ApiDebug(pcmCopyHandle->api, "%s: write update ESTRPIPE", cardid); suspend(pcmOut, (int)availOut); continue; } @@ -606,7 +628,8 @@ static void *writeThreadEntry(void *handle) { if (nbWritten <= 0) { if (nbWritten == -EPIPE) { int err = xrun(pcmOut, (int)nbWritten); - AFB_ApiDebug(pcmCopyHandle->api, "XXX write EPIPE (%d), recov %d", ++pcmCopyHandle->write_err_count , err); + AFB_ApiDebug(pcmCopyHandle->api, "XXX %s write EPIPE (%d), recov %d", + pcmCopyHandle->pcmOut->cid.cardid, ++pcmCopyHandle->write_err_count , err); continue; } else if (nbWritten == -ESTRPIPE) { @@ -620,30 +643,70 @@ static void *writeThreadEntry(void *handle) { } } - +done: pthread_exit(0); return NULL; } PUBLIC int AlsaPcmCopyMuteSignal(SoftMixerT *mixer, AlsaPcmCtlT *pcmIn, bool mute) { - ssize_t ret = write(pcmIn->muteFd, &mute, sizeof(mute)); + + PcmCopyEvent event; + event.eventType = mute?PCM_COPY_MUTE:PCM_COPY_UNMUTE; + ssize_t ret = write(pcmIn->eventFd, &event, sizeof(event)); (void) ret; return 0; } +static int AlsaPcmCopyEndSignal(SoftMixerT *mixer, AlsaPcmCtlT *pcmIn) { + PcmCopyEvent event; + event.eventType = PCM_COPY_END; + ssize_t ret = write(pcmIn->eventFd, &event, sizeof(event)); + (void) ret; + return 0; +} + +static int AlsaPcmCopyEnd(SoftMixerT *mixer, AlsaPcmCopyHandleT * handle) { + + handle->ending = true; + // wake up the reader through the eventfd + AlsaPcmCopyEndSignal(mixer, handle->pcmIn); + // wake up the writer through the wait semaphore + sem_post(&handle->sem); -PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT *pcmIn, AlsaPcmCtlT *pcmOut, AlsaPcmHwInfoT * opts) { + return 0; +} + +PUBLIC int AlsaPcmCopyStop(SoftMixerT *mixer, AlsaPcmCopyHandleT * handle) { + + AFB_ApiDebug(mixer->api, "%s: Stopping copy threads of %s", __func__, handle->stream->uid); + + AlsaPcmCopyEnd(mixer, handle); + + if (pthread_join(handle->wthread, NULL) != 0) + AFB_ApiDebug(mixer->api, "%s: Failed to join write thread", __func__); + + if (pthread_join(handle->rthread, NULL) != 0) + AFB_ApiDebug(mixer->api, "%s: Failed to join read thread", __func__); + + sem_destroy(&handle->sem); + + AFB_ApiDebug(mixer->api, "%s: Copy threads of %s are STOPPED", __func__, handle->stream->uid); + + return 0; + +} + +PUBLIC int AlsaPcmCopyStart(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT *pcmIn, AlsaPcmCtlT *pcmOut, AlsaPcmHwInfoT * opts) { char string[32]; int error; + AlsaPcmCopyHandleT *cHandle=NULL; // Fulup need to check https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m___direct.html AlsaDumpPcmInfo(mixer,"PcmIn",pcmIn->handle); AlsaDumpPcmInfo(mixer,"PcmOut",pcmOut->handle); - AFB_ApiInfo(mixer->api, "%s: Configure CAPTURE PCM", __func__); - /* remember configuration of capture */ pcmIn->params = (AlsaPcmHwInfoT*)malloc(sizeof(AlsaPcmHwInfoT)); memcpy(pcmIn->params, opts, sizeof(AlsaPcmHwInfoT)); @@ -654,7 +717,7 @@ PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT pcmIn->mixer = mixer; pcmOut->mixer = mixer; - AFB_ApiInfo(mixer->api, "%s: Configure CAPTURE PCM", __func__); + AFB_ApiDebug(mixer->api, "%s: Configure CAPTURE PCM", __func__); // prepare PCM for capture and replay error = AlsaPcmConf(mixer, pcmIn, SND_PCM_STREAM_CAPTURE); @@ -663,7 +726,7 @@ PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT goto OnErrorExit; } - AFB_ApiInfo(mixer->api, "%s: Configure PLAYBACK PCM", __func__); + AFB_ApiDebug(mixer->api, "%s: Configure PLAYBACK PCM", __func__); // input and output should match error = AlsaPcmConf(mixer, pcmOut, SND_PCM_STREAM_PLAYBACK); @@ -696,26 +759,35 @@ PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT goto OnErrorExit; }; - AlsaPcmCopyHandleT *cHandle= calloc(1, sizeof(AlsaPcmCopyHandleT)); + cHandle = calloc(1, sizeof(AlsaPcmCopyHandleT)); + if (cHandle == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto OnErrorExit; + } cHandle->info = "pcmCpy"; cHandle->pcmIn = pcmIn; cHandle->pcmOut = pcmOut; cHandle->api = mixer->api; cHandle->channels = opts->channels; + cHandle->stream = stream; cHandle->frame_size = (snd_pcm_format_physical_width(opts->format) / 8) * opts->channels; + AFB_ApiDebug(mixer->api, "%s: Frame size is %zu", __func__, cHandle->frame_size); + AFB_ApiDebug(mixer->api, "%s: Buffer delay is %d ms", __func__, stream->delayms); - AFB_ApiInfo(mixer->api, "%s: Frame size is %zu", __func__, cHandle->frame_size); - - snd_pcm_uframes_t nbFrames = 2 * opts->rate; // Exactly 2 second of buffer + snd_pcm_uframes_t nbFrames = (stream->delayms * opts->rate)/1000; cHandle->rbuf = alsa_ringbuf_new(nbFrames, cHandle->frame_size); + if (!cHandle->rbuf) { + SOFTMIXER_NOMEM(mixer->api); + goto OnErrorExit; + } cHandle->read_err_count = 0; cHandle->write_err_count = 0; - AFB_ApiInfo(mixer->api, "%s Copy buffer nbframes is %zu", __func__, nbFrames); + AFB_ApiDebug(mixer->api, "%s Copy buffer: nbframes is %zu", __func__, nbFrames); // get FD poll descriptor for capture PCM int pcmInCount = snd_pcm_poll_descriptors_count(pcmIn->handle); @@ -724,17 +796,16 @@ PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT "%s: Fail pcmIn=%s get fds count error=%s", __func__, ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); goto OnErrorExit; - }; + } - if (pcmInCount > 1) { - AFB_ApiError(mixer->api, - "%s: Fail, pcmIn=%s; having more than one FD on capture PCM is not supported (here, %d)", - __func__, ALSA_PCM_UID(pcmOut->handle, string) , pcmInCount); + cHandle->nbPcmFds = pcmInCount+1; + cHandle->pollFds = (struct pollfd *) malloc((cHandle->nbPcmFds)*sizeof(struct pollfd)); + if (cHandle->pollFds == NULL){ + SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } - struct pollfd pcmInFd; - if ((error = snd_pcm_poll_descriptors(pcmIn->handle, &pcmInFd, 1)) < 0) { + if ((error = snd_pcm_poll_descriptors(pcmIn->handle, cHandle->pollFds+1, pcmInCount)) < 0) { AFB_ApiError(mixer->api, "%s: Fail pcmIn=%s get pollfds error=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); @@ -742,27 +813,22 @@ PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT }; // create the mute pipe - int pMuteFd[2]; - error = pipe(pMuteFd); + int eventFdPipe[2]; + error = pipe(eventFdPipe); if (error < 0) { AFB_ApiError(mixer->api, "Unable to create the mute signaling pipe"); goto OnErrorExit; } - struct pollfd mutePFd; + struct pollfd * eventPollFd = &cHandle->pollFds[0]; // read end - mutePFd.fd = pMuteFd[0]; - mutePFd.events = POLLIN; - mutePFd.revents = 0; + eventPollFd->fd = eventFdPipe[0]; + eventPollFd->events = POLLIN; + eventPollFd->revents = 0; // write end - pcmIn->muteFd = pMuteFd[1]; - - cHandle->pollFds[0] = mutePFd; - cHandle->pollFds[1] = pcmInFd; - - cHandle->nbPcmFds = pcmInCount+1; + pcmIn->eventFd = eventFdPipe[1]; error = sem_init(&cHandle->sem, 0 , 0); if (error < 0) { @@ -818,11 +884,22 @@ PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT __func__, ALSA_PCM_UID(pcmOut->handle, string), strerror(error)); } + stream->copy = cHandle; + return 0; OnErrorExit: AFB_ApiError(mixer->api, "%s: - pcmIn=%s" , __func__, ALSA_PCM_UID(pcmIn->handle, string)); AFB_ApiError(mixer->api, "%s: - pcmOut=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string)); + + if (cHandle &&cHandle->pollFds) { + free (cHandle->pollFds); + cHandle->pollFds = NULL; + } + + if (cHandle) + free(cHandle); + return -1; } diff --git a/plugins/alsa/alsa-effect-ramp.c b/plugins/alsa/alsa-effect-ramp.c index 312bdfa..e51879d 100644 --- a/plugins/alsa/alsa-effect-ramp.c +++ b/plugins/alsa/alsa-effect-ramp.c @@ -84,7 +84,7 @@ PUBLIC int AlsaVolRampApply(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaStreamA long curvol, newvol; const char *uid, *volS; json_object *volJ; - int error, index; + int error; uint64_t usec; int count = 0; @@ -93,7 +93,9 @@ PUBLIC int AlsaVolRampApply(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaStreamA , "volume", &volJ ); if (error) { - AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s invalid-json should {uid:ramp, vol:[+,-,=]value} ramp=%s", mixer->uid, stream->uid, json_object_get_string(rampJ)); + AFB_ApiError(mixer->api, + "%s: mixer=%s stream=%s invalid-json should {uid:ramp, vol:[+,-,=]value} ramp=%s", + __func__, mixer->uid, stream->uid, json_object_get_string(rampJ)); goto OnErrorExit; } @@ -125,7 +127,9 @@ PUBLIC int AlsaVolRampApply(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaStreamA } if (count != 1) { - AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s invalid-numeric expect {uid:%s, vol:[+,-,=]value} get vol:%s", mixer->uid, stream->uid, uid, json_object_get_string(volJ)); + AFB_ApiError(mixer->api, + "%s: mixer=%s stream=%s invalid-numeric expect {uid:%s, vol:[+,-,=]value} get vol:%s", + __func__, mixer->uid, stream->uid, uid, json_object_get_string(volJ)); goto OnErrorExit; } break; @@ -134,35 +138,47 @@ PUBLIC int AlsaVolRampApply(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaStreamA break; default: - AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s invalid-type expect {uid:%s, vol:[+,-,=]value} get vol:%s", mixer->uid, stream->uid, uid, json_object_get_string(volJ)); + AFB_ApiError(mixer->api, + "%s :mixer=%s stream=%s invalid-type expect {uid:%s, vol:[+,-,=]value} get vol:%s", + __func__, mixer->uid, stream->uid, uid, json_object_get_string(volJ)); goto OnErrorExit; } error = AlsaCtlNumidGetLong(mixer, sndcard, stream->volume, &curvol); if (error) { - AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s ramp=%s Fail to get volume from numid=%d", mixer->uid, stream->uid, uid, stream->volume); + AFB_ApiError(mixer->api, + "%s: mixer=%s stream=%s ramp=%s Fail to get volume from numid=%d", + __func__, mixer->uid, stream->uid, uid, stream->volume); goto OnErrorExit; } // search for ramp uid in mixer - for (index=0; index<= mixer->max.ramps; index++) { - if (!strcasecmp(mixer->ramps[index]->uid, uid)) { + AlsaVolRampT * ramp; + bool found = false; + cds_list_for_each_entry(ramp, &mixer->ramps.list, list) { + if (ramp->uid && !strcasecmp(ramp->uid, uid)) { break; } } - if (index == mixer->max.ramps) { - AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s ramp=%s does not exit", mixer->uid, stream->uid, uid); + if (!found) { + AFB_ApiError(mixer->api, + "%s: mixer=%s stream=%s ramp=%s does not exit", + __func__, mixer->uid, stream->uid, uid); goto OnErrorExit; } VolRampHandleT *rHandle = calloc(1, sizeof (VolRampHandleT)); + if (rHandle == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto OnErrorExit; + } rHandle->uid = stream->uid; rHandle->numid = stream->volume; rHandle->sndcard = sndcard; rHandle->mixer = mixer; - rHandle->ramp = mixer->ramps[index]; + rHandle->ramp = ramp; rHandle->target = newvol; rHandle->current = curvol; rHandle->sdLoop = mixer->sdLoop; diff --git a/plugins/alsa/alsa-plug-dmix.c b/plugins/alsa/alsa-plug-dmix.c index 3c6d62e..d63b79f 100644 --- a/plugins/alsa/alsa-plug-dmix.c +++ b/plugins/alsa/alsa-plug-dmix.c @@ -28,14 +28,20 @@ ALSA_PLUG_PROTO(dmix); PUBLIC AlsaPcmCtlT* AlsaCreateDmix(SoftMixerT *mixer, const char* pcmName, AlsaSndPcmT *pcmSlave, int open) { snd_config_t *dmixConfig = NULL, *slaveConfig = NULL, *elemConfig = NULL, *pcmConfig=NULL; - AlsaPcmCtlT *pcmPlug= calloc(1,sizeof(AlsaPcmCtlT)); AlsaSndCtlT *sndSlave=pcmSlave->sndcard; + + AlsaPcmCtlT *pcmPlug = AlsaPcmCtlNew(mixer, pcmName); + if (pcmPlug == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto OnErrorExit; + } + pcmPlug->cid.cardid=pcmName; char * fullPcmName = NULL; int error=0; - AFB_ApiInfo(mixer->api, "%s: %s, slave %s, cardid %s (dev %d, subdev %d)\n", + AFB_ApiDebug(mixer->api, "%s: %s, slave %s, cardid %s (dev %d, subdev %d)", __func__, pcmName, pcmSlave->uid, @@ -46,9 +52,7 @@ PUBLIC AlsaPcmCtlT* AlsaCreateDmix(SoftMixerT *mixer, const char* pcmName, AlsaS error = asprintf(&fullPcmName,"%s,%d,%d", sndSlave->cid.cardid, sndSlave->cid.device, sndSlave->cid.subdev); if (error == -1) { - AFB_ApiError(mixer->api, - "%s: Insufficient memory", - __func__); + SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; } @@ -92,7 +96,7 @@ PUBLIC AlsaPcmCtlT* AlsaCreateDmix(SoftMixerT *mixer, const char* pcmName, AlsaS * Trying to set another value later leads to silent failure * */ - error += snd_config_imake_integer(&elemConfig, "channels", pcmSlave->ccount); + error += snd_config_imake_integer(&elemConfig, "channels", pcmSlave->nbChannels); if (error) goto OnErrorExit; error += snd_config_add(slaveConfig, elemConfig); if (error) goto OnErrorExit; diff --git a/plugins/alsa/alsa-plug-rate.c b/plugins/alsa/alsa-plug-rate.c index 085ad78..b2c43d2 100644 --- a/plugins/alsa/alsa-plug-rate.c +++ b/plugins/alsa/alsa-plug-rate.c @@ -23,10 +23,18 @@ ALSA_PLUG_PROTO(rate); -PUBLIC AlsaPcmCtlT* AlsaCreateRate(SoftMixerT *mixer, const char* pcmName, AlsaPcmCtlT *pcmSlave, AlsaPcmHwInfoT *params, int open) { +PUBLIC AlsaPcmCtlT* AlsaCreateRate(SoftMixerT *mixer, AlsaStreamAudioT * stream, const char* pcmName, AlsaPcmCtlT *pcmSlave, AlsaPcmHwInfoT *params, int open) { snd_config_t *rateConfig, *slaveConfig, *elemConfig, *pcmConfig; - AlsaPcmCtlT *pcmPlug = calloc(1, sizeof (AlsaPcmCtlT)); + + AFB_ApiDebug(mixer->api,"%s : %s", __func__, pcmName); + + AlsaPcmCtlT *pcmPlug = AlsaPcmCtlNew(mixer, pcmName); + if (!pcmPlug) { + SOFTMIXER_NOMEM(mixer->api); + goto OnErrorExit; + } + pcmPlug->cid.cardid = pcmName; int error = 0; @@ -59,24 +67,28 @@ PUBLIC AlsaPcmCtlT* AlsaCreateRate(SoftMixerT *mixer, const char* pcmName, AlsaP error += snd_config_search(snd_config, "pcm", &pcmConfig); error += snd_config_add(pcmConfig, rateConfig); if (error) { - AFB_ApiError(mixer->api, "AlsaCreateRate: fail to add configRATE=%s", pcmPlug->cid.cardid); + AFB_ApiError(mixer->api, "%s: fail to add config RATE=%s", __func__, pcmPlug->cid.cardid); goto OnErrorExit; } + stream->rateConfig = rateConfig; + if (open) error = _snd_pcm_rate_open(&pcmPlug->handle, pcmPlug->cid.cardid, snd_config, rateConfig, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (error) { - AFB_ApiError(mixer->api, "AlsaCreateRate: fail to create Rate=%s Slave=%s Error=%s", pcmPlug->cid.cardid, pcmSlave->cid.cardid, snd_strerror(error)); + AFB_ApiError(mixer->api, + "%s: fail to create Rate=%s Slave=%s Error=%s", + __func__, pcmPlug->cid.cardid, pcmSlave->cid.cardid, snd_strerror(error)); goto OnErrorExit; } // Debug config & pcm //AlsaDumpCtlConfig(mixer, "plug-rate", pcmConfig, 1); - //AlsaDumpCtlConfig (mixer, "plug-rate", rateConfig, 1); - AFB_ApiNotice(mixer->api, "AlsaCreateRate: %s done", pcmPlug->cid.cardid); + AlsaDumpCtlConfig (mixer, "plug-rate", rateConfig, 1); + AFB_ApiNotice(mixer->api, "%s: %s done", __func__, pcmPlug->cid.cardid); return pcmPlug; OnErrorExit: AlsaDumpCtlConfig(mixer, "plug-rate", rateConfig, 1); - AFB_ApiNotice(mixer->api, "AlsaCreateRate: OnErrorExit"); + AFB_ApiNotice(mixer->api, "%s: OnErrorExit", __func__); return NULL; } diff --git a/plugins/alsa/alsa-plug-route.c b/plugins/alsa/alsa-plug-route.c index 2699701..c00c0cf 100644 --- a/plugins/alsa/alsa-plug-route.c +++ b/plugins/alsa/alsa-plug-route.c @@ -26,37 +26,42 @@ ALSA_PLUG_PROTO(route); typedef struct { const char *uid; const char *cardid; + const char * pcmplug_params; int cardidx; int ccount; int port; + bool isPcmPlug; } ChannelCardPortT; STATIC int CardChannelByUid(SoftMixerT *mixer, const char *uid, ChannelCardPortT *response) { + + AlsaSndPcmT * pcm = NULL; bool found = false; // search for channel within all sound card sink (channel port target is computed by order) - for (int idx = 0; mixer->sinks[idx]; idx++) { - int jdx; - AlsaSndPcmT * pcm = mixer->sinks[idx]; - AlsaPcmChannelT **channels = pcm->channels; + cds_list_for_each_entry(pcm, &mixer->sinks.list, list) { - if (!channels) { + if (pcm->nbChannels == 0) { AFB_ApiError(mixer->api, "%s: No Sink card=%s [should declare channels]", __func__, pcm->uid); goto OnErrorExit; } - for (jdx = 0; jdx < pcm->ccount; jdx++) { - if (!strcasecmp(channels[jdx]->uid, uid)) { - response->port = channels[jdx]->port; - response->uid = pcm->uid; - response->ccount = pcm->ccount; - response->cardid = pcm->sndcard->cid.cardid; - response->cardidx = pcm->sndcard->cid.cardidx; - found = true; - break; - } + AlsaPcmChannelT * channel = NULL; + cds_list_for_each_entry(channel, &pcm->channels.list, list) { + + if (channel->uid && !strcasecmp(channel->uid, uid)) { + response->port = channel->port; + response->uid = pcm->uid; + response->ccount = pcm->nbChannels; + response->cardid = pcm->sndcard->cid.cardid; + response->cardidx = pcm->sndcard->cid.cardidx; + response->pcmplug_params = pcm->sndcard->cid.pcmplug_params; + response->isPcmPlug= pcm->isPcmPlug; + found = true; + break; + } } } @@ -75,78 +80,141 @@ OnErrorExit: PUBLIC AlsaPcmCtlT* AlsaCreateRoute(SoftMixerT *mixer, AlsaSndZoneT *zone, int open) { snd_config_t *routeConfig, *elemConfig, *slaveConfig, *tableConfig, *pcmConfig; - int scount=0, error = 0; - ChannelCardPortT slave, channel; - AlsaPcmCtlT *pcmRoute = calloc(1, sizeof (AlsaPcmCtlT)); + int error = 0; + ChannelCardPortT slave, channelCardPort; + AlsaPcmCtlT *pcmRoute = NULL; + char *cardid = NULL; - char *dmixUid = NULL; + char *slaveUid = NULL; - if (!mixer->sinks) { - AFB_ApiError(mixer->api, "AlsaCreateRoute: mixer=%s zone(%s)(zone) No sink found [should Registry sound card first]", mixer->uid, zone->uid); - goto OnErrorExit; + AFB_ApiDebug(mixer->api, "%s (zone %s)", __func__, zone->uid); + + if (mixer->nbSinks == 0) { + AFB_ApiError(mixer->api, "%s: mixer=%s zone(%s)(zone) No sink found [should Registry sound card first]", __func__, mixer->uid, zone->uid); + goto fail; } - if (asprintf(&cardid, "route-%s", zone->uid) == -1) - goto OnErrorExit; + if (asprintf(&cardid, "route-%s", zone->uid) == -1) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } + + pcmRoute = AlsaPcmCtlNew(mixer, cardid); + if (!pcmRoute) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } pcmRoute->cid.cardid = (const char *) cardid; pcmRoute->params = ApiSinkGetParamsByZone(mixer, zone->uid); - zone->params=pcmRoute->params; + if (pcmRoute->params == NULL) { + AFB_ApiError(mixer->api, "%s: Failed to retrieve zone parameters", __func__); + goto fail_nodump; + } + + zone->params = pcmRoute->params; // use 1st zone channel to retrieve sound card name + channel count. - error = CardChannelByUid(mixer, zone->sinks[0]->uid, &slave); + + AlsaPcmChannelT * channel = cds_list_first_entry(&zone->sinks.list, AlsaPcmChannelT, list); + error = CardChannelByUid(mixer, channel->uid, &slave); if (error) { - AFB_ApiError(mixer->api, "AlsaCreateRoute:zone(%s) fail to find channel=%s", zone->uid, zone->sinks[0]->uid); - goto OnErrorExit; + AFB_ApiError(mixer->api, "%s:zone(%s) fail to find channel=%s", __func__, zone->uid, channel->uid); + goto fail; } - // move from hardware to DMIX attach to sndcard - if (asprintf(&dmixUid, "dmix-%s", slave.uid) == -1) - goto OnErrorExit; + // DMIX only supports real hardware as a slave + + if (!slave.isPcmPlug) { + // move from hardware to DMIX attach to sndcard + if (asprintf(&slaveUid, "dmix-%s", slave.uid) == -1) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } + } else { + slaveUid = strdup(slave.pcmplug_params); + if (!slaveUid) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } + pcmRoute->isPcmPlug = true; + } // temporary store to unable multiple channel to route to the same port - snd_config_t **cports = alloca(slave.ccount * sizeof (void*)); - memset(cports, 0, slave.ccount * sizeof (void*)); + + snd_config_t **cports = alloca(zone->nbSinks * sizeof (void*)); + memset(cports, 0, zone->nbSinks * sizeof (void*)); int zcount = 0; // We create 1st ttable to retrieve sndcard slave and channel count (void)snd_config_make_compound(&tableConfig, "ttable", 0); - for (scount = 0; zone->sinks[scount] != NULL; scount++) { + cds_list_for_each_entry(channel, &zone->sinks.list, list) { - error = CardChannelByUid(mixer, zone->sinks[scount]->uid, &channel); + AFB_ApiDebug(mixer->api, "%s: zone->sink channel %s ", __func__, channel->uid); + + error = CardChannelByUid(mixer, channel->uid, &channelCardPort); if (error) { - AFB_ApiError(mixer->api, "AlsaCreateRoute:zone(%s) fail to find channel=%s", zone->uid, zone->sinks[scount]->uid); - goto OnErrorExit; + AFB_ApiError(mixer->api, "%s: zone(%s) fail to find channel=%s", __func__, zone->uid, channel->uid); + goto fail; } - if (slave.cardidx != channel.cardidx) { - AFB_ApiError(mixer->api, "AlsaCreateRoute:zone(%s) cannot span over multiple sound card %s != %s ", zone->uid, slave.cardid, channel.cardid); - goto OnErrorExit; + if (slave.cardidx != channelCardPort.cardidx) { + AFB_ApiError(mixer->api, + "%s: zone(%s) cannot span over multiple sound card %s != %s ", + __func__, zone->uid, slave.cardid, channelCardPort.cardid); + goto fail; } - int port = zone->sinks[scount]->port; - int target = channel.port; + int target = channelCardPort.port; + int port = channel->port; + double volume = 1.0; // currently only support 100% - // if channel entry does not exit into ttable create it now - if (!cports[port]) { - zcount++; + // if channel entry does not exist into ttable create it now + if (!cports[port]) { + char channelS[4]; // 999 channel should be more than enough snprintf(channelS, sizeof (channelS), "%d", port); - error += snd_config_make_compound(&cports[port], channelS, 0); - error += snd_config_add(tableConfig, cports[port]); + + error = snd_config_make_compound(&cports[port], channelS, 0); + if (error) { + AFB_ApiError(mixer->api, "%s: make_compound failed , err %s", __func__, snd_strerror(error)); + goto fail; + } + + error = snd_config_add(tableConfig, cports[port]); + if (error) { + AFB_ApiError(mixer->api, "%s: add compound to table failed, err %s", __func__, snd_strerror(error)); + goto fail; + } } + zcount++; + // ttable require target port as a table and volume as a value char targetS[4]; snprintf(targetS, sizeof (targetS), "%d", target); - error += snd_config_imake_real(&elemConfig, targetS, volume); - error += snd_config_add(cports[port], elemConfig); - if (error) goto OnErrorExit; + + if (channel->volume != -1) + volume = channel->volume; + else + volume = 1.0; + + error = snd_config_imake_real(&elemConfig, targetS, volume); + if (error) { + AFB_ApiError(mixer->api, "%s: Failed to interpret volume real value, err %s", __func__, snd_strerror(error)); + goto fail; + } + + error = snd_config_add(cports[port], elemConfig); + if (error) { + AFB_ApiError(mixer->api, "%s Failed to add ttable entry:err=%s", __func__, snd_strerror(error)); + goto fail; + } + } - if (error) goto OnErrorExit; // update zone with route channel count and sndcard params pcmRoute->ccount = zcount; @@ -155,36 +223,36 @@ PUBLIC AlsaPcmCtlT* AlsaCreateRoute(SoftMixerT *mixer, AlsaSndZoneT *zone, int o // refresh global alsalib config and create PCM top config snd_config_update(); error += snd_config_top(&routeConfig); - if (error) goto OnErrorExit; + if (error) goto fail; error += snd_config_set_id(routeConfig, cardid); - if (error) goto OnErrorExit; + if (error) goto fail; error += snd_config_imake_string(&elemConfig, "type", "route"); - if (error) goto OnErrorExit; + if (error) goto fail; error += snd_config_add(routeConfig, elemConfig); - if (error) goto OnErrorExit; + if (error) goto fail; error += snd_config_make_compound(&slaveConfig, "slave", 0); - if (error) goto OnErrorExit; - error += snd_config_imake_string(&elemConfig, "pcm", dmixUid); - if (error) goto OnErrorExit; + if (error) goto fail; + error += snd_config_imake_string(&elemConfig, "pcm", slaveUid); + if (error) goto fail; error += snd_config_add(slaveConfig, elemConfig); - if (error) goto OnErrorExit; + if (error) goto fail; error += snd_config_imake_integer(&elemConfig, "channels", slave.ccount); - if (error) goto OnErrorExit; + if (error) goto fail; error += snd_config_add(slaveConfig, elemConfig); - if (error) goto OnErrorExit; + if (error) goto fail; error += snd_config_add(routeConfig, slaveConfig); - if (error) goto OnErrorExit; + if (error) goto fail; error += snd_config_add(routeConfig, tableConfig); - if (error) goto OnErrorExit; + if (error) goto fail; if (open) error = _snd_pcm_route_open(&pcmRoute->handle, pcmRoute->cid.cardid, snd_config, routeConfig, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (error) { AFB_ApiError(mixer->api, "%s: zone(%s) fail to create Plug=%s error=%s", __func__, zone->uid, pcmRoute->cid.cardid, snd_strerror(error)); - goto OnErrorExit; + goto fail; } snd_config_update(); @@ -194,20 +262,28 @@ PUBLIC AlsaPcmCtlT* AlsaCreateRoute(SoftMixerT *mixer, AlsaSndZoneT *zone, int o AFB_ApiError(mixer->api, "%s: %s fail to add config route=%s error=%s", __func__, zone->uid, pcmRoute->cid.cardid, snd_strerror(error)); - goto OnErrorExit; + goto fail; } + zone->routeConfig = routeConfig; + // Debug config & pcm AFB_ApiNotice(mixer->api, "%s: zone(%s) DONE", __func__, zone->uid); AlsaDumpCtlConfig(mixer, "plug-route", routeConfig, 1); + return pcmRoute; -OnErrorExit: +fail: +// AFB_ApiError(mixer->api, "%s ERROR, DUMPING PCM...", __func__); +// AlsaDumpCtlConfig(mixer, "plug-pcm", snd_config, 1); + AFB_ApiError(mixer->api, "%s ERROR, DUMPING ROUTE...", __func__); + AlsaDumpCtlConfig(mixer, "plug-route", routeConfig, 1); + +fail_nodump: free(pcmRoute); free(cardid); - free(dmixUid); - AlsaDumpCtlConfig(mixer, "plug-pcm", snd_config, 1); - AlsaDumpCtlConfig(mixer, "plug-route", routeConfig, 1); + free(slaveUid); + AFB_ApiNotice(mixer->api, "%s: zone(%s) FAIL", __func__, zone->uid); return NULL; } diff --git a/plugins/alsa/alsa-plug-vol.c b/plugins/alsa/alsa-plug-vol.c index ce4627a..67400a5 100644 --- a/plugins/alsa/alsa-plug-vol.c +++ b/plugins/alsa/alsa-plug-vol.c @@ -22,16 +22,27 @@ ALSA_PLUG_PROTO(softvol); // stream uses softvol plugin +PUBLIC void AlsaDeleteSoftvol(SoftMixerT *mixer, AlsaPcmCtlT * ctl) { + +} + PUBLIC AlsaPcmCtlT *AlsaCreateSoftvol(SoftMixerT *mixer, AlsaStreamAudioT *stream, char* slaveid, AlsaSndCtlT *sndcard, char* ctlName, int max, int open) { snd_config_t *streamConfig, *elemConfig, *slaveConfig, *controlConfig,*pcmConfig; - AlsaPcmCtlT *pcmVol= calloc(1,sizeof(AlsaPcmCtlT)); + char *cardid = NULL; int error = 0; - - AFB_ApiInfo(mixer->api, "%s create SOFTVOL on %s", __func__, slaveid); + AlsaPcmCtlT *pcmVol = NULL; - char *cardid = NULL; - if (asprintf(&cardid, "softvol-%s", stream->uid) == -1) + AFB_ApiDebug(mixer->api, "%s create SOFTVOL on %s", __func__, slaveid); + if (asprintf(&cardid, "softvol-%s", stream->uid) == -1) { + SOFTMIXER_NOMEM(mixer->api); goto OnErrorExit; + } + + pcmVol = AlsaPcmCtlNew(mixer, cardid); + if (pcmVol == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto OnErrorExit; + } pcmVol->cid.cardid = (const char *) cardid; @@ -82,6 +93,8 @@ PUBLIC AlsaPcmCtlT *AlsaCreateSoftvol(SoftMixerT *mixer, AlsaStreamAudioT *strea goto OnErrorExit; } + stream->softvolConfig = streamConfig; + // Debug config & pcm //AlsaDumpCtlConfig (mixer, "plug-config", pcmConfig, 1); AlsaDumpCtlConfig(mixer, "plug-softvol", streamConfig, 1); diff --git a/plugins/alsa/alsa-ringbuf.c b/plugins/alsa/alsa-ringbuf.c index 476222c..2dd1d0d 100644 --- a/plugins/alsa/alsa-ringbuf.c +++ b/plugins/alsa/alsa-ringbuf.c @@ -3,8 +3,14 @@ alsa_ringbuf_t * alsa_ringbuf_new(snd_pcm_uframes_t capacity, size_t frameSize) { alsa_ringbuf_t * rb = malloc(sizeof(alsa_ringbuf_t)); + if (!rb) + goto fail; + rb->rbuf = ringbuf_new(capacity*frameSize); + if (!rb->rbuf) + goto fail; rb->frameSize = frameSize; +fail: return rb; } diff --git a/plugins/alsa/alsa-softmixer.h b/plugins/alsa/alsa-softmixer.h index e7218bd..a8cc6de 100644 --- a/plugins/alsa/alsa-softmixer.h +++ b/plugins/alsa/alsa-softmixer.h @@ -21,7 +21,6 @@ #ifndef _ALSA_SOFTMIXER_ #define _ALSA_SOFTMIXER_ -//#include #include #include #include @@ -31,11 +30,15 @@ #include #include +#include + #include "ctl-plugin.h" #include "wrap-json.h" #include "alsa-ringbuf.h" +#include "alsa-transaction.h" + #ifndef PUBLIC #define PUBLIC #endif @@ -51,7 +54,7 @@ #define ALSA_CARDID_MAX_LEN 64 -#define SMIXER_SUBDS_CTLS 3 +#define SMIXER_SUBDS_CTLS 16 #define SMIXER_DEFLT_LOOPS 4 #define SMIXER_DEFLT_SINKS 8 #define SMIXER_DEFLT_SOURCES 32 @@ -59,9 +62,16 @@ #define SMIXER_DEFLT_STREAMS 32 #define SMIXER_DEFLT_RAMPS 8 +#define SMIXER_DEFLT_DELAYMS 1000 + +#define SOFTMIXER_VERB_NONE "none" + #define ALSA_PLUG_PROTO(plugin) \ int _snd_pcm_ ## plugin ## _open(snd_pcm_t **pcmp, const char *name, snd_config_t *root, snd_config_t *conf, snd_pcm_stream_t stream, int mode) +#define SOFTMIXER_NOMEM(api) \ + AFB_ApiError((api), "%s: Insufficient memory", __func__) + // auto switch from Log to API request depending on request presence. #define AFB_IfReqFailF(mixer, request, status, format, ...) \ if (request) AFB_ReqFailF(request, status, format, __VA_ARGS__); \ @@ -101,16 +111,20 @@ typedef struct { } AlsaPcmHwInfoT; typedef struct { + const char * name; int ccount; bool mute; - int muteFd; + int eventFd; AlsaDevInfoT cid; snd_pcm_t *handle; AlsaPcmHwInfoT *params; - void * mixer; + struct SoftMixerT_ * mixer; snd_pcm_uframes_t avail_min; + + bool closeAtDeletion; // intermediate pcms in the pcm chain must not be closed, else it make libasound abort() + bool isPcmPlug; } AlsaPcmCtlT; typedef struct { @@ -138,12 +152,15 @@ typedef struct { char* info; int nbPcmFds; - struct pollfd pollFds[2]; + struct pollfd * pollFds; sem_t sem; pthread_mutex_t mutex; int saveFd; + bool ending; + + struct AlsaStreamAudioT_ * stream; } AlsaPcmCopyHandleT; @@ -156,87 +173,112 @@ typedef struct { long step; } AlsaSndControlT; + typedef struct { const char*uid; int port; + double volume; + struct cds_list_head list; } AlsaPcmChannelT; - typedef struct { const char *uid; int delay; // delay between volset in us int stepDown; // linear % int stepUp; // linear % + struct cds_list_head list; } AlsaVolRampT; - - - typedef struct { int numid; RegistryNumidT type; AlsaPcmCtlT *pcm; + struct cds_list_head cardListEntry; /* node to list in AlsaSndCtlT */ + struct AlsaSndCtlT_ *sndcard; /* ref to owner */ } RegistryEntryPcmT; -typedef struct { - long rcount; + +typedef struct AlsaSndCtlT_ { AlsaDevInfoT cid; snd_ctl_t *ctl; AlsaPcmHwInfoT *params; - RegistryEntryPcmT **registry; + long nbRegistry; + struct cds_list_head registryList; + struct SubscribeHandleT_ * eventSubscribeHandle; } AlsaSndCtlT; typedef struct { const char *uid; - AlsaPcmChannelT **sources; - AlsaPcmChannelT **sinks; + unsigned int nbSources; + AlsaPcmChannelT sources; + unsigned int nbSinks; + AlsaPcmChannelT sinks; int ccount; AlsaPcmHwInfoT *params; + struct cds_list_head list; + snd_config_t * routeConfig; + bool isPcmPlug; } AlsaSndZoneT; typedef struct { const char *uid; const char *verb; - unsigned int ccount; AlsaSndCtlT *sndcard; AlsaSndControlT volume; AlsaSndControlT mute; - AlsaPcmChannelT **channels; + unsigned int nbChannels; + AlsaPcmChannelT channels; snd_pcm_stream_t direction; + struct cds_list_head list; + bool isPcmPlug; + void * apiVerbHandle; } AlsaSndPcmT; typedef struct { const char*uid; int index; int numid; + struct cds_list_head list; } AlsaLoopSubdevT; -typedef struct { + +struct SoftMixerT_; + +typedef struct AlsaSndLoopT { const char *uid; + struct SoftMixerT_ * mixer; /* owner */ int playback; int capture; - long scount; AlsaSndCtlT *sndcard; - AlsaLoopSubdevT **subdevs; + int nbSubdevs; + AlsaLoopSubdevT subdevs; + struct cds_list_head list; } AlsaSndLoopT; -typedef struct { +typedef struct AlsaStreamAudioT_ { const char *uid; const char *verb; const char *info; const char *sink; + const char *playback; const char *source; const char *ramp; int volume; int mute; + unsigned int delayms; AlsaPcmHwInfoT *params; AlsaPcmCopyHandleT *copy; + struct cds_list_head list; /* link to the global list*/ + AlsaPcmCtlT * softvol; + snd_config_t * softvolConfig; + snd_config_t * rateConfig; + void * verbApiHandle; } AlsaStreamAudioT; -typedef struct { +typedef struct SoftMixerT_{ const char *uid; const char *info; AFB_ApiT api; @@ -250,17 +292,27 @@ typedef struct { unsigned int streams; unsigned int ramps; } max; - AlsaSndLoopT **loops; - AlsaSndPcmT **sinks; - AlsaSndPcmT **sources; - AlsaSndZoneT **zones; - AlsaStreamAudioT **streams; - AlsaVolRampT **ramps; + unsigned int nbLoops; + AlsaSndLoopT loops; + unsigned int nbSinks; + AlsaSndPcmT sinks; + unsigned int nbSources; + AlsaSndPcmT sources; + unsigned int nbZones; + AlsaSndZoneT zones; + unsigned int nbStreams; + AlsaStreamAudioT streams; + unsigned int nbRamps; + AlsaVolRampT ramps; + + AlsaMixerTransaction * transaction; } SoftMixerT; // alsa-utils-bypath.c PUBLIC snd_ctl_card_info_t *AlsaByPathInfo(SoftMixerT *mixer, const char *devpath); -PUBLIC AlsaPcmCtlT *AlsaByPathOpenPcm(SoftMixerT *mixer, AlsaDevInfoT *pcmId, snd_pcm_stream_t direction); +PUBLIC AlsaPcmCtlT * AlsaPcmCtlNew(SoftMixerT*, const char*); +PUBLIC void AlsaPcmCtlDelete(SoftMixerT *mixer, void *); +PUBLIC AlsaPcmCtlT *AlsaByPathOpenPcmCtl(SoftMixerT *mixer, AlsaDevInfoT *pcmId, snd_pcm_stream_t direction); PUBLIC snd_ctl_t *AlsaByPathOpenCtl(SoftMixerT *mixer, const char *uid, AlsaSndCtlT *dev); // alsa-utils-dump.c @@ -279,7 +331,7 @@ PUBLIC void AlsaDumpCtlConfig(SoftMixerT *mixer, const char* info, snd_config_t // alsa-core-ctl.c PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNumidElemId(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numid) ; PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName) ; -PUBLIC snd_ctl_t *AlsaCtlOpenCtl(SoftMixerT *mixer, const char *cardid) ; + PUBLIC int CtlElemIdGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, snd_ctl_elem_id_t *elemId, long *value) ; PUBLIC int CtlElemIdSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, snd_ctl_elem_id_t *elemId, long value) ; PUBLIC snd_ctl_card_info_t *AlsaCtlGetCardInfo(SoftMixerT *mixer, const char *cardid) ; @@ -288,37 +340,55 @@ PUBLIC int AlsaCtlNumidGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numi PUBLIC int AlsaCtlNameSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName, long value) ; PUBLIC int AlsaCtlNameGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName, long* value) ; PUBLIC int AlsaCtlCreateControl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, char* ctlName, int ctlCount, int ctlMin, int ctlMax, int ctlStep, long value) ; -PUBLIC snd_ctl_t* AlsaCrlFromPcm(SoftMixerT *mixer, snd_pcm_t *pcm) ; + PUBLIC int AlsaCtlSubscribe(SoftMixerT *mixer, const char *uid, AlsaSndCtlT *sndcard) ; + +PUBLIC void AlsaCtlUnregister(SoftMixerT* mixer, void *); PUBLIC int AlsaCtlRegister(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaPcmCtlT *pcmdev, RegistryNumidT type, int numid); // alsa-core-pcm.c PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode); -PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT *pcmIn, AlsaPcmCtlT *pcmOut, AlsaPcmHwInfoT * opts); +PUBLIC int AlsaPcmCopyStart(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT *pcmIn, AlsaPcmCtlT *pcmOut, AlsaPcmHwInfoT * opts); +PUBLIC int AlsaPcmCopyStop(SoftMixerT *mixer, AlsaPcmCopyHandleT * handle); // alsa-plug-*.c _snd_pcm_PLUGIN_open_ see macro ALSA_PLUG_PROTO(plugin) -PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *streamAudio, AlsaPcmCtlT *pcmIn, AlsaPcmCtlT *pcmOut, AlsaPcmHwInfoT * opts); PUBLIC int AlsaPcmCopyMuteSignal(SoftMixerT *mixer, AlsaPcmCtlT *pcmIn, bool mute); + PUBLIC AlsaPcmCtlT* AlsaCreateSoftvol(SoftMixerT *mixer, AlsaStreamAudioT *stream, char *slaveid, AlsaSndCtlT *sndcard, char* ctlName, int max, int open); PUBLIC AlsaPcmCtlT* AlsaCreateRoute(SoftMixerT *mixer, AlsaSndZoneT *zone, int open); -PUBLIC AlsaPcmCtlT* AlsaCreateRate(SoftMixerT *mixer, const char* pcmName, AlsaPcmCtlT *pcmSlave, AlsaPcmHwInfoT *params, int open); +PUBLIC AlsaPcmCtlT* AlsaCreateRate(SoftMixerT *mixer, AlsaStreamAudioT *stream, const char* pcmName, AlsaPcmCtlT *pcmSlave, AlsaPcmHwInfoT *params, int open); PUBLIC AlsaPcmCtlT* AlsaCreateDmix(SoftMixerT *mixer, const char* pcmName, AlsaSndPcmT *pcmSlave, int open); // alsa-api-* + +PUBLIC int ApiLoopAttach(SoftMixerT *mixer, AFB_ReqT request, const char *, json_object * argsJ); +PUBLIC int ApiSourceAttach(SoftMixerT *mixer, AFB_ReqT request, const char *, json_object * argsJ); +PUBLIC int ApiSinkAttach(SoftMixerT *mixer, AFB_ReqT request, const char *, json_object * argsJ); +PUBLIC int ApiStreamAttach(SoftMixerT *mixer, AFB_ReqT request, const char * uid, const char *prefix, json_object * argsJ); +PUBLIC int ApiZoneAttach(SoftMixerT *mixer, AFB_ReqT request, const char *, json_object * argsJ); +PUBLIC int ApiRampAttach(SoftMixerT *mixer, AFB_ReqT request, const char *, json_object *argsJ); + +// helper used for attach verb,and also by the streams API for fake zones +PUBLIC AlsaSndZoneT * zoneCreate(SoftMixerT* mixer, const char * uid, json_object * argsJ); + PUBLIC AlsaLoopSubdevT *ApiLoopFindSubdev(SoftMixerT *mixer, const char *streamUid, const char *targetUid, AlsaSndLoopT **loop); -PUBLIC int ApiLoopAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ); + PUBLIC AlsaPcmHwInfoT *ApiPcmSetParams(SoftMixerT *mixer, const char *uid, json_object *paramsJ); +PUBLIC void ApiPcmDelParams(SoftMixerT*, AlsaPcmHwInfoT*); + PUBLIC AlsaSndPcmT *ApiPcmAttachOne(SoftMixerT *mixer, const char *uid, snd_pcm_stream_t direction, json_object *argsJ); +PUBLIC AlsaSndPcmT * ApiPcmNew(SoftMixerT* mixer); +PUBLIC void ApiPcmDelete(SoftMixerT * mixer, AlsaSndPcmT * pcm); + PUBLIC AlsaVolRampT *ApiRampGetByUid(SoftMixerT *mixer, const char *uid); -PUBLIC int ApiRampAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object *argsJ); + PUBLIC AlsaPcmHwInfoT *ApiSinkGetParamsByZone(SoftMixerT *mixer, const char *target); -PUBLIC int ApiSinkAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ); + PUBLIC AlsaSndPcmT *ApiSinkGetByUid(SoftMixerT *mixer, const char *target); PUBLIC AlsaSndCtlT *ApiSourceFindSubdev(SoftMixerT *mixer, const char *target); -PUBLIC int ApiSourceAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ); -PUBLIC int ApiStreamAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, const char *prefix, json_object * argsJ); + + PUBLIC AlsaSndZoneT *ApiZoneGetByUid(SoftMixerT *mixer, const char *target); -PUBLIC int ApiZoneAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ); // alsa-effect-ramp.c PUBLIC AlsaVolRampT *ApiRampGetByUid(SoftMixerT *mixer, const char *uid); diff --git a/plugins/alsa/alsa-transaction.c b/plugins/alsa/alsa-transaction.c new file mode 100644 index 0000000..bd32603 --- /dev/null +++ b/plugins/alsa/alsa-transaction.c @@ -0,0 +1,111 @@ +#include +#include + +#include "alsa-softmixer.h" +#include "alsa-transaction.h" +#include "wrap-json.h" + +AlsaMixerTransaction * AlsaMixerTransactionNew(struct SoftMixerT_ * mixer, const char * uid) { + AlsaMixerTransaction * newList = (AlsaMixerTransaction *) malloc(sizeof(AlsaMixerTransaction)); + if (newList == NULL) + goto fail; + + CDS_INIT_LIST_HEAD(&newList->list); + newList->uid = strdup(uid); + if (newList->uid == NULL) { + goto fail_list; + } + + newList->mixer = mixer; + + return newList; + +fail_list: + free(newList); +fail: + return NULL; +} + +void AlsaMixerTransactionDataListDestroy(AlsaMixerTransaction* list) { + free(list); +} + +bool AlsaMixerTransactionObjectAdd(AlsaMixerTransaction* list, void* object, AlsaTransactionDestructor destructor) { + bool ret = false; + AlsaMixerTransactionDataItem * newItem = NULL; + if (!list) + goto fail; + + newItem = (AlsaMixerTransactionDataItem *) malloc(sizeof(AlsaMixerTransactionDataItem)); + if (newItem == NULL) + goto fail; + + CDS_INIT_LIST_HEAD(&newItem->list_entry); + newItem->object = object; + newItem->destructor = destructor; + cds_list_add(&newItem->list_entry, &list->list); + + ret = true; +fail: + return ret; + +} + +void AlsaMixerTransactionDoCleanup(AlsaMixerTransaction* transaction) { + AlsaMixerTransactionDataItem * item, *sav; + + cds_list_for_each_entry_safe(item, sav, &transaction->list, list_entry) { + if (item->destructor) + item->destructor(transaction->mixer, item->object); + + cds_list_del(&item->list_entry); + free(item); + } +} + +void AlsaMixerTransactionVerbCB(AFB_ReqT request) { + json_object *responseJ = NULL; + AlsaMixerTransaction *transaction = (AlsaMixerTransaction*) afb_req_get_vcbdata(request); + json_object *argsJ = afb_req_json(request); + int error; + char * action = NULL; + const char * uid; + + error = wrap_json_unpack(argsJ, "{ss!}", + "action", &action); + + if (error) { + AFB_ReqFailF(request, "missing action", "%s: missing 'action' field: %s", transaction->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + uid = strdup(transaction->uid); + if (!uid) { + SOFTMIXER_NOMEM(transaction->mixer->api); + goto OnErrorExit; + } + + if (strcmp(action, "remove") == 0) { + AlsaMixerTransactionDoCleanup(transaction); + error = afb_api_del_verb(transaction->mixer->api, transaction->uid, (void**)transaction); + if (error) { + AFB_ReqFail(request, "verb deletion" , "verb was not removed"); + goto OnErrorExit; + } + + } else { + AFB_ReqFailF(request, "unsupported action", "%s: unsupported action %s (supported ones are ['remove']", transaction->uid, action); + goto OnErrorExit; + } + + responseJ=json_object_new_object(); + json_object_object_add(responseJ, "result", json_object_new_string("OK")); + AFB_ReqSuccess(request, responseJ, uid); + + free((char*)uid); + return; + +OnErrorExit: + return; + +} diff --git a/plugins/alsa/alsa-transaction.h b/plugins/alsa/alsa-transaction.h new file mode 100644 index 0000000..a0af0ae --- /dev/null +++ b/plugins/alsa/alsa-transaction.h @@ -0,0 +1,37 @@ +#ifndef __INC_ALSA_TRANSACTION_H +#define __INC_ALSA_TRANSACTION_H + +#include +#include + +#include "afb-definitions.h" + +struct SoftMixerT_; + +typedef void (*AlsaTransactionDestructor) (struct SoftMixerT_ *, void*); + +typedef struct { + const char * uid; + struct SoftMixerT_* mixer; + struct cds_list_head list; +} AlsaMixerTransaction; + +typedef struct { + struct cds_list_head list_entry; + void * object; + AlsaTransactionDestructor destructor; +} AlsaMixerTransactionDataItem; + +extern AlsaMixerTransaction * AlsaMixerTransactionNew(struct SoftMixerT_ * api, const char*); + +extern void AlsaMixerTransactionDataListDestroy(AlsaMixerTransaction*); + +extern bool AlsaMixerTransactionObjectAdd(AlsaMixerTransaction*, void * object, AlsaTransactionDestructor destructor); + +extern void AlsaMixerTransactionDoCleanup(AlsaMixerTransaction*); + +extern bool AlsaMixerTransactionVerbCreate(AlsaMixerTransaction*); + +extern void AlsaMixerTransactionVerbCB(AFB_ReqT request); + +#endif /* __INC_ALSA_TRANSACTION_H */ diff --git a/plugins/alsa/alsa-utils-bypath.c b/plugins/alsa/alsa-utils-bypath.c index 2427e9f..908d247 100644 --- a/plugins/alsa/alsa-utils-bypath.c +++ b/plugins/alsa/alsa-utils-bypath.c @@ -27,7 +27,6 @@ #include #include "alsa-softmixer.h" -#include "alsa-bluez.h" // extracted IOCTLs from #define _IOR_HACKED(type,nr,size) _IOC(_IOC_READ,(type),(nr),size) @@ -38,52 +37,126 @@ PUBLIC snd_ctl_card_info_t *AlsaByPathInfo(SoftMixerT *mixer, const char *devpath) { int open_dev; - snd_ctl_card_info_t *cardInfo = malloc(snd_ctl_card_info_sizeof()); - - if (!devpath) goto OnErrorExit; + snd_ctl_card_info_t *cardInfo = (snd_ctl_card_info_t *) malloc(snd_ctl_card_info_sizeof()); + if (!devpath) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } open_dev = open(devpath, O_RDONLY); - if (open_dev < 0) goto OnErrorExit; + if (open_dev < 0) { + AFB_ApiError(mixer->api, "%s: Unable to open %s", __func__, devpath); + goto fail_cardinfo; + } int rc = ioctl(open_dev, SNDRV_CTL_IOCTL_CARD_INFO(snd_ctl_card_info_sizeof()), cardInfo); if (rc < 0) { - close(open_dev); - goto OnErrorExit; + AFB_ApiError(mixer->api, "%s: ioctl on %s failed", __func__, devpath); + goto fail_dev; } close(open_dev); return cardInfo; -OnErrorExit: - AFB_ApiError(mixer->api, "AlsaCardInfoByPath: fail to find sndcard by path= %s", devpath); + +fail_dev: + close(open_dev); +fail_cardinfo: + free(cardInfo); +fail: return NULL; } -PUBLIC AlsaPcmCtlT *AlsaByPathOpenPcm(SoftMixerT *mixer, AlsaDevInfoT *pcmDev, snd_pcm_stream_t direction) { +PUBLIC AlsaPcmCtlT * AlsaPcmCtlNew(SoftMixerT* mixer, const char * name) { + + AFB_ApiDebug(mixer->api, "%s: NEW %s",__func__, name); + + AlsaPcmCtlT *pcmCtl = calloc(1, sizeof (AlsaPcmCtlT)); + if (pcmCtl == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } + + pcmCtl->name = name; + pcmCtl->closeAtDeletion = false; + + AlsaMixerTransactionObjectAdd(mixer->transaction, pcmCtl, AlsaPcmCtlDelete); + +fail: + return pcmCtl; +} + +PUBLIC void AlsaPcmCtlDelete(SoftMixerT* mixer, void * arg) { + int error = 0; + + AlsaPcmCtlT * pcmCtl = (AlsaPcmCtlT *) arg; + + if (!pcmCtl->closeAtDeletion) + goto done; + + if (pcmCtl->isPcmPlug) { + AFB_ApiDebug(mixer->api, "%s: Wont' close '%s' PCM, its slave is a plug", __func__, pcmCtl->cid.cardid); + goto done; + } + + AFB_ApiDebug(mixer->api, "%s: Closing %s",__func__, pcmCtl->cid.cardid); + error = snd_pcm_close(pcmCtl->handle); + if (error) { + // It's safe to ignore this error. In case of ioplug PCM, the device may have already disappeared + AFB_ApiDebug(mixer->api, + "%s: failed to close capture PCM %s: err=%s", __func__, pcmCtl->cid.cardid, snd_strerror(error)); + } + +done: + + free((char*) pcmCtl->cid.cardid); + free(pcmCtl); +} + +PUBLIC AlsaPcmCtlT *AlsaByPathOpenPcmCtl(SoftMixerT *mixer, AlsaDevInfoT *pcmDev, snd_pcm_stream_t direction) { int error; - AlsaPcmCtlT *pcmCtl = calloc(1, sizeof (AlsaPcmCtlT)); char *cardid = NULL; + AlsaPcmCtlT *pcmCtl = NULL; + if (pcmDev->pcmplug_params) { - pcmDev->cardid = pcmDev->pcmplug_params; + pcmDev->cardid = strdup(pcmDev->pcmplug_params); + if (pcmDev->cardid == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } } if (!pcmDev->cardid) { if (pcmDev->subdev) { - if (asprintf(&cardid, "hw:%i,%i,%i", pcmDev->cardidx, pcmDev->device, pcmDev->subdev) == -1) - goto OnErrorExit; + if (asprintf(&cardid, "hw:%i,%i,%i", pcmDev->cardidx, pcmDev->device, pcmDev->subdev) == -1) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } } else if (pcmDev->device) { - if (asprintf(&cardid, "hw:%i,%i", pcmDev->cardidx, pcmDev->device) == -1) - goto OnErrorExit; + if (asprintf(&cardid, "hw:%i,%i", pcmDev->cardidx, pcmDev->device) == -1) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } } else { - if (asprintf(&cardid, "hw:%i", pcmDev->cardidx) == -1) - goto OnErrorExit; + if (asprintf(&cardid, "hw:%i", pcmDev->cardidx) == -1) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } } + pcmDev->cardid = (const char*)cardid; } + pcmCtl = AlsaPcmCtlNew(mixer, pcmDev->cardid); + if (pcmCtl == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto fail_cardid; + } + + // inherit CID from pcmDev pcmCtl->cid.cardid = pcmDev->cardid; pcmCtl->cid.cardidx = pcmDev->cardidx; @@ -92,21 +165,23 @@ PUBLIC AlsaPcmCtlT *AlsaByPathOpenPcm(SoftMixerT *mixer, AlsaDevInfoT *pcmDev, s pcmCtl->cid.name=NULL; pcmCtl->cid.longname=NULL; - AFB_ApiInfo(mixer->api, + AFB_ApiDebug(mixer->api, "%s OPEN PCM '%s', direction %s", __func__, pcmDev->cardid, direction==SND_PCM_STREAM_PLAYBACK?"playback":"capture"); error = snd_pcm_open(&pcmCtl->handle, pcmCtl->cid.cardid, direction, SND_PCM_NONBLOCK); if (error < 0) { AFB_ApiError(mixer->api, "%s: fail openpcm cardid=%s error=%s", __func__, pcmCtl->cid.cardid, snd_strerror(error)); - goto OnErrorExit; + goto fail_pcmctl; } return pcmCtl; -OnErrorExit: +fail_pcmctl: free(pcmCtl); - free(cardid); +fail_cardid: + free((char*)pcmDev->cardid); +fail: return NULL; } @@ -117,43 +192,63 @@ PUBLIC snd_ctl_t *AlsaByPathOpenCtl(SoftMixerT *mixer, const char *uid, AlsaSndC // get card info from /dev/snd/xxx if not use hw:x,x,x snd_ctl_card_info_t *cardInfo = NULL; + AFB_ApiDebug(mixer->api, "%s: devpath %s, cardid %s, plug %s", __func__, dev->cid.devpath, dev->cid.cardid, dev->cid.pcmplug_params); + if (dev->cid.devpath) cardInfo = AlsaByPathInfo(mixer, dev->cid.devpath); else if (dev->cid.cardid) cardInfo = AlsaCtlGetCardInfo(mixer, dev->cid.cardid); - else if (dev->cid.pcmplug_params) + else if (dev->cid.pcmplug_params) { + AFB_ApiDebug(mixer->api, "Get card info from plug params %s", dev->cid.pcmplug_params); cardInfo = AlsaCtlGetCardInfo(mixer, dev->cid.pcmplug_params); + } if (!cardInfo) { AFB_ApiError(mixer->api, "%s: uid=%s fail to find sndcard by path=%s id=%s", __func__, uid, dev->cid.devpath, dev->cid.cardid); - goto OnErrorExit; + goto fail; } // extract useful info from cardInfo handle dev->cid.devpath = NULL; dev->cid.cardidx = snd_ctl_card_info_get_card(cardInfo); dev->cid.name = strdup(snd_ctl_card_info_get_name(cardInfo)); + if (dev->cid.name == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto fail; + } dev->cid.longname = strdup(snd_ctl_card_info_get_longname(cardInfo)); + if (dev->cid.longname == NULL) { + SOFTMIXER_NOMEM(mixer->api); + goto fail_cid_name; + } // build a valid name and open sndcard - if (asprintf((char**) &dev->cid.cardid, "hw:%i", dev->cid.cardidx) == -1) - goto OnErrorExit; + if (asprintf((char**) &dev->cid.cardid, "hw:%i", dev->cid.cardidx) == -1) { + SOFTMIXER_NOMEM(mixer->api); + goto fail_cid_longname; + } if ((err = snd_ctl_open(&handle, dev->cid.cardid, 0)) < 0) { AFB_ApiError(mixer->api, "%s uid=%s sndcard open fail cardid=%s longname=%s error=%s", __func__, uid, dev->cid.cardid, dev->cid.longname, snd_strerror(err)); - goto OnErrorExit; + goto fail_cardid; } AFB_ApiNotice(mixer->api, - "%s: uid=%s cardid=%s cardname=%s", - __func__, uid, dev->cid.cardid, dev->cid.longname); + "%s: uid=%s cardid=%s cardname=%s pcmplug %s", + __func__, uid, dev->cid.cardid, dev->cid.longname, dev->cid.pcmplug_params); free(cardInfo); return handle; -OnErrorExit: +fail_cid_longname: + free((char*)dev->cid.longname); +fail_cid_name: + free((char*)dev->cid.name); +fail_cardid: + free((char*)dev->cid.cardid); +fail: return NULL; } diff --git a/plugins/alsa/alsa-utils-dump.c b/plugins/alsa/alsa-utils-dump.c index b6c011d..79059ee 100644 --- a/plugins/alsa/alsa-utils-dump.c +++ b/plugins/alsa/alsa-utils-dump.c @@ -109,7 +109,7 @@ PUBLIC void AlsaDumpCtlSubdev(SoftMixerT *mixer, snd_ctl_t *handle) { while (1) { if (snd_ctl_pcm_next_device(handle, &dev) < 0) { - AFB_ApiError(mixer->api, "AlsaDumpCard: fail to open subdev card id=%s name=%s", cardId, cardName); + AFB_ApiError(mixer->api, "%s: fail to open subdev card id=%s name=%s", __func__, cardId, cardName); goto OnErrorExit; } @@ -119,12 +119,13 @@ PUBLIC void AlsaDumpCtlSubdev(SoftMixerT *mixer, snd_ctl_t *handle) { // ignore empty device slot if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { if (err != -ENOENT) - AFB_ApiError(mixer->api, "control digital audio info (%s): %s", cardName, snd_strerror(err)); + AFB_ApiError(mixer->api, "%s: control digital audio info (%s): %s", __func__, cardName, snd_strerror(err)); continue; } - AFB_ApiDebug(mixer->api, "AlsaDumpCard card %d: %s [%s], device %d: %s [%s]", - cardIndex, cardId, cardName, dev, snd_pcm_info_get_id(pcminfo), snd_pcm_info_get_name(pcminfo)); + AFB_ApiDebug(mixer->api, + "%s card %d: %s [%s], device %d: %s [%s]", + __func__, cardIndex, cardId, cardName, dev, snd_pcm_info_get_id(pcminfo), snd_pcm_info_get_name(pcminfo)); // loop on subdevices subdevCount = snd_pcm_info_get_subdevices_count(pcminfo); @@ -133,12 +134,12 @@ PUBLIC void AlsaDumpCtlSubdev(SoftMixerT *mixer, snd_ctl_t *handle) { for (unsigned int idx = 0; idx < subdevCount; idx++) { snd_pcm_info_set_subdevice(pcminfo, idx); if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { - AFB_ApiError(mixer->api, "AlsaDumpCard: control digital audio playback info %i: %s", cardIndex, snd_strerror(err)); + AFB_ApiError(mixer->api, "%s: control digital audio playback info %i: %s", __func__, cardIndex, snd_strerror(err)); } else { - AFB_ApiDebug(mixer->api, "AlsaDumpCard: -- Subdevice #%d: %s", idx, snd_pcm_info_get_subdevice_name(pcminfo)); + AFB_ApiDebug(mixer->api, "%s: -- Subdevice #%d: %s", __func__, idx, snd_pcm_info_get_subdevice_name(pcminfo)); } } - AFB_ApiDebug(mixer->api, "AlsaDumpCard => subdevice count=%d avaliable=%d", subdevCount, subdevAvail); + AFB_ApiDebug(mixer->api, "%s => subdevice count=%d avaliable=%d", __func__, subdevCount, subdevAvail); } return; @@ -153,7 +154,7 @@ PUBLIC void AlsaDumpPcmParams(SoftMixerT *mixer, snd_pcm_hw_params_t *pcmHwParam snd_output_buffer_open(&output); snd_pcm_hw_params_dump(pcmHwParams, output); snd_output_buffer_string(output, &buffer); - AFB_ApiDebug(mixer->api, "%s:\n%s",__func__, buffer); + AFB_ApiDebug(mixer->api, "%s:\n--------------\n%s\n-------------",__func__, buffer); snd_output_close(output); } diff --git a/plugins/alsa/ringbuf.c b/plugins/alsa/ringbuf.c index b18ddb6..c24f278 100644 --- a/plugins/alsa/ringbuf.c +++ b/plugins/alsa/ringbuf.c @@ -46,7 +46,7 @@ ringbuf_new(size_t capacity) /* One byte is used for detecting the full condition. */ rb->size = capacity + 1; - rb->buf = malloc(rb->size); + rb->buf = calloc(1, rb->size); if (rb->buf) ringbuf_reset(rb); else { -- cgit 1.2.3-korg