diff options
author | Mark Farrugia <mark.farrugia@fiberdyne.com.au> | 2019-02-12 12:11:26 +1100 |
---|---|---|
committer | Mark Farrugia <mark.farrugia@fiberdyne.com.au> | 2019-02-12 12:12:38 +1100 |
commit | d5ac69750d6d16c88ee009e0106bfd6eeffaa695 (patch) | |
tree | 56026a1dc912f49d76ebc24fa3c27b2c9a7e82dd /plugins/alsa | |
parent | d5cc09d1ef5c58bb8d34e3a47c391a10d8b48181 (diff) |
Add support for AVIRT
Leverage the new AVIRT driver for a more secure, more dynamically
configurable loopback sound driver.
To use, replace the file smixer-4a-default.json with
smixer-4a-avirt.json, at /usr/libexec/agl/smixer/etc.
The snd-avirt drivers are installed in AGL by default as of 6.99.2.
The existing snd-aloop configuration is not broken by this change.
Change-Id: I827636656c109a7393ad77997e05069a2462ea46
Signed-off-by: Mark Farrugia <mark.farrugia@fiberdyne.com.au>
Diffstat (limited to 'plugins/alsa')
-rw-r--r-- | plugins/alsa/alsa-api-loop.c | 307 | ||||
-rw-r--r-- | plugins/alsa/alsa-api-mixer.c | 33 | ||||
-rw-r--r-- | plugins/alsa/alsa-api-streams.c | 23 | ||||
-rw-r--r-- | plugins/alsa/alsa-softmixer.h | 9 |
4 files changed, 278 insertions, 94 deletions
diff --git a/plugins/alsa/alsa-api-loop.c b/plugins/alsa/alsa-api-loop.c index a123bb3..c0ea549 100644 --- a/plugins/alsa/alsa-api-loop.c +++ b/plugins/alsa/alsa-api-loop.c @@ -19,8 +19,50 @@ #define _GNU_SOURCE // needed for vasprintf #include "alsa-softmixer.h" +#include <avirt/avirt.h> #include <string.h> +#define UID_AVIRT_LOOP "AVIRT-Loopback" + +#define REGNUMID_0 51 +#define REGNUMID_1 57 +#define REGNUMID_2 63 +#define REGNUMID_3 69 +#define REGNUMID_4 75 +#define REGNUMID_5 81 +#define REGNUMID_6 87 +#define REGNUMID_7 93 +#define REGNUMID_8 99 +#define REGNUMID_9 105 +#define REGNUMID_10 111 +#define REGNUMID_11 117 +#define REGNUMID_12 123 +#define REGNUMID_13 129 +#define REGNUMID_14 135 +#define REGNUMID_15 141 + +struct RegistryNumidMap { + int index; + int numid; +} numidmap[] = { + { 0, REGNUMID_0 }, + { 1, REGNUMID_1 }, + { 2, REGNUMID_2 }, + { 3, REGNUMID_3 }, + { 4, REGNUMID_4 }, + { 5, REGNUMID_5 }, + { 6, REGNUMID_6 }, + { 7, REGNUMID_7 }, + { 8, REGNUMID_8 }, + { 9, REGNUMID_9 }, + { 10, REGNUMID_10 }, + { 11, REGNUMID_11 }, + { 12, REGNUMID_12 }, + { 13, REGNUMID_13 }, + { 14, REGNUMID_14 }, + { 15, REGNUMID_15 }, +}; + 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 @@ -55,6 +97,90 @@ fail: return NULL; } +STATIC int CheckOneSubdev(SoftMixerT *mixer, AlsaSndLoopT *loop, AlsaLoopSubdevT *subdev) { + // create loop subdev entry point with cardidx+device+subdev in order to open subdev and not sndcard + AlsaDevInfoT loopSubdev; + loopSubdev.devpath=NULL; + loopSubdev.cardid=NULL; + loopSubdev.pcmplug_params = NULL; + loopSubdev.cardidx = loop->sndcard->cid.cardidx; + loopSubdev.device = loop->capture; + loopSubdev.subdev = subdev->index; + + // assert we may open this loopback subdev in capture mode + AlsaPcmCtlT *pcmCtl = AlsaByPathOpenPcmCtl(mixer, &loopSubdev, SND_PCM_STREAM_CAPTURE); + if (!pcmCtl) { + AFB_API_ERROR(mixer->api, "%s: Failed", __func__); + return -1; + } + + pcmCtl->closeAtDeletion = true; + + // free PCM as we only open loop to assert it's a valid capture device + AlsaMixerTransactionObjectForget(mixer->transaction, pcmCtl); + AlsaPcmCtlDelete(mixer, pcmCtl); + + return 0; +} + +STATIC AlsaLoopSubdevT *ProcessOneAvirtSubdev(SoftMixerT *mixer, AlsaSndLoopT *loop, json_object *streamJ, int index) { + char *uid, *zone_uid; + AlsaSndZoneT *zone; + AFB_API_INFO(mixer->api, "%s: %d", __func__, index); + int error; + + uid = alloca(32); + + error = wrap_json_unpack(streamJ, "{ss,s?s,s?s,ss,s?s,s?i,s?b,s?o,s?s !}" + , "uid", &uid + , "verb", NULL + , "info", NULL + , "zone", &zone_uid + , "source", NULL + , "volume", NULL + , "mute", NULL + , "params", NULL + , "ramp", NULL + ); + if (error) { + AFB_API_NOTICE(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 (mixer->nbZones != 0) { + zone = ApiZoneGetByUid(mixer, zone_uid); + if (!zone) + goto OnErrorExit; + } else { + AFB_API_ERROR(mixer->api, "%s: No zones defined!", __func__); + goto OnErrorExit; + } + + error = snd_avirt_stream_new(uid, zone->ccount, + SND_PCM_STREAM_PLAYBACK, "ap_loopback"); + if (error < 0) { + AFB_API_ERROR(mixer->api, + "%s: mixer=%s stream=%s could not create AVIRT stream [errno=%d]", + __func__, mixer->uid, uid, error); + return NULL; + } + + AlsaLoopSubdevT *subdev = calloc(1, sizeof (AlsaPcmCtlT)); + subdev->uid = NULL; + subdev->index = index; + subdev->numid = numidmap[index].index; + + if (CheckOneSubdev(mixer, loop, subdev) < 0) + return NULL; + + return subdev; + +OnErrorExit: + AFB_API_ERROR(mixer->api, "%s fail", __func__); + return NULL; +} STATIC AlsaLoopSubdevT *ProcessOneSubdev(SoftMixerT *mixer, AlsaSndLoopT *loop, json_object *subdevJ) { @@ -87,26 +213,8 @@ STATIC AlsaLoopSubdevT *ProcessOneSubdev(SoftMixerT *mixer, AlsaSndLoopT *loop, } } - // create loop subdev entry point with cardidx+device+subdev in order to open subdev and not sndcard - AlsaDevInfoT loopSubdev; - loopSubdev.devpath=NULL; - loopSubdev.cardid=NULL; - loopSubdev.pcmplug_params = NULL; - loopSubdev.cardidx = loop->sndcard->cid.cardidx; - loopSubdev.device = loop->capture; - loopSubdev.subdev = subdev->index; - - // assert we may open this loopback subdev in capture mode - 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 - AlsaMixerTransactionObjectForget(mixer->transaction, pcmCtl); - AlsaPcmCtlDelete(mixer, pcmCtl); + if (CheckOneSubdev(mixer, loop, subdev) < 0) + goto fail_subdev_uid; AFB_API_DEBUG(mixer->api, "%s DONE", __func__); @@ -126,7 +234,7 @@ static void freeSubdev(SoftMixerT* mixer, AlsaLoopSubdevT * subdev) { } -STATIC AlsaSndLoopT *AttachOneLoop(SoftMixerT *mixer, const char *uid, json_object *argsJ) { +STATIC AlsaSndLoopT *AttachOneLoop(SoftMixerT *mixer, const char *uid, json_object *argsJ, json_object *streamsJ) { json_object *subdevsJ = NULL, *devicesJ = NULL; int error; @@ -152,67 +260,122 @@ STATIC AlsaSndLoopT *AttachOneLoop(SoftMixerT *mixer, const char *uid, json_obje loop->sndcard = sndctl; - error = wrap_json_unpack(argsJ, "{ss,s?s,s?s,so,so !}" + error = wrap_json_unpack(argsJ, "{ss,s?s,s?s,s?o,s?o !}" , "uid", &loop->uid , "path", &loop->sndcard->cid.devpath , "cardid", &loop->sndcard->cid.cardid , "devices", &devicesJ , "subdevs", &subdevsJ ); - if (error || !loop->uid || !subdevsJ || (!loop->sndcard->cid.devpath && !loop->sndcard->cid.cardid)) { - AFB_API_NOTICE(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 fail_snd_card; + + if (loop->uid) { + if (!strcmp(loop->uid, UID_AVIRT_LOOP)) + loop->avirt = true; + else + loop->avirt = false; } - // try to open sound card control interface - loop->sndcard->ctl = AlsaByPathOpenCtl(mixer, loop->uid, loop->sndcard); - if (!loop->sndcard->ctl) { - AFB_API_ERROR(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); + if (error || !loop->uid || (!loop->avirt && !subdevsJ) || (!loop->sndcard->cid.devpath && !loop->sndcard->cid.cardid)) { + AFB_API_NOTICE(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 fail_snd_card; } - // Default devices is playback=0 capture=1 - if (!devicesJ) { - loop->playback = 0; - loop->capture = 1; - } else { - error = wrap_json_unpack(devicesJ, "{si,si !}", "capture", &loop->capture, "playback", &loop->playback); - if (error) { - AFB_API_NOTICE(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 fail_snd_card_ctl; + if (!loop->avirt) { + // try to open sound card control interface + loop->sndcard->ctl = AlsaByPathOpenCtl(mixer, loop->uid, loop->sndcard); + if (!loop->sndcard->ctl) { + AFB_API_ERROR(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 fail_snd_card; } - } - AlsaLoopSubdevT * subdev; + // Default devices is playback=0 capture=1 + if (!devicesJ) { + loop->playback = 0; + loop->capture = 1; + } else { + error = wrap_json_unpack(devicesJ, "{si,si !}", "capture", &loop->capture, "playback", &loop->playback); + if (error) { + AFB_API_NOTICE(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 fail_snd_card_ctl; + } + } - switch (json_object_get_type(subdevsJ)) { - case json_type_object: - 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: { - 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); - subdev = ProcessOneSubdev(mixer, loop, subdevJ); - if (!subdev) { - goto fail_loop_subdev; - } - loop->nbSubdevs++; - cds_list_add_tail(&subdev->list, &loop->subdevs.list); + AlsaLoopSubdevT * subdev; + + switch (json_object_get_type(subdevsJ)) { + case json_type_object: + 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: { + 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); + subdev = ProcessOneSubdev(mixer, loop, subdevJ); + if (!subdev) { + goto fail_loop_subdev; + } + loop->nbSubdevs++; + cds_list_add_tail(&subdev->list, &loop->subdevs.list); + } + break; } - break; - } - default: - AFB_API_ERROR(mixer->api, "%s mixer=%s hal=%s Loop=%s invalid subdevs= %s", - __func__, mixer->uid, uid, loop->uid, json_object_get_string(subdevsJ)); - goto fail_snd_card_ctl; + default: + AFB_API_ERROR(mixer->api, "%s mixer=%s hal=%s Loop=%s invalid subdevs= %s", + __func__, mixer->uid, uid, loop->uid, json_object_get_string(subdevsJ)); + goto fail_snd_card_ctl; + } + } else { // loop->avirt == true + AFB_API_NOTICE(mixer->api, "nbStreams: %d max.streams: %d", mixer->nbStreams, mixer->max.streams); + + if (mixer->nbStreams >= mixer->max.streams) + goto fail_snd_card_ctl; + + AlsaLoopSubdevT * subdev; + + // Create AVIRT streams + switch (json_object_get_type(streamsJ)) { + case json_type_object: + subdev = ProcessOneAvirtSubdev(mixer, loop, streamsJ, 0); + if (!subdev) + goto fail_loop_subdev; + loop->nbSubdevs++; + cds_list_add_tail(&subdev->list, &loop->subdevs.list); + break; + case json_type_array: { + int nbSubDevs = (int) json_object_array_length(streamsJ); + if (nbSubDevs >= mixer->max.streams) + goto fail_loop_subdev; + for (int idx = 0; idx < nbSubDevs; idx++) { + json_object *streamJ = json_object_array_get_idx(streamsJ, idx); + subdev = ProcessOneAvirtSubdev(mixer, loop, streamJ, idx); + if (!subdev) { + goto fail_loop_subdev; + } + loop->nbSubdevs++; + cds_list_add_tail(&subdev->list, &loop->subdevs.list); + } + break; + } + default: + goto fail_snd_card_ctl; + } + + snd_avirt_card_seal(); + + // try to open sound card control interface + loop->sndcard->ctl = AlsaByPathOpenCtl(mixer, loop->uid, loop->sndcard); + if (!loop->sndcard->ctl) { + AFB_API_ERROR(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 fail_snd_card; + } } AFB_API_NOTICE(mixer->api, "%s, uid %s DONE", __func__, uid); @@ -258,9 +421,9 @@ static void loopDestroy(SoftMixerT * mixer, void* arg) { } -static AlsaSndLoopT * loopCreate(SoftMixerT *mixer, const char *uid, json_object *argsJ) { +static AlsaSndLoopT * loopCreate(SoftMixerT *mixer, const char *uid, json_object *argsJ, json_object *streamsJ) { - AlsaSndLoopT * newLoop = AttachOneLoop(mixer, uid, argsJ); + AlsaSndLoopT * newLoop = AttachOneLoop(mixer, uid, argsJ, streamsJ); if (!newLoop) { goto fail; } @@ -276,7 +439,7 @@ fail: return newLoop; } -PUBLIC int ApiLoopAttach(SoftMixerT *mixer, afb_req_t request, const char * uid, json_object * argsJ) { +PUBLIC int ApiLoopAttach(SoftMixerT *mixer, afb_req_t request, const char * uid, json_object * argsJ, json_object *streamsJ) { AlsaSndLoopT * newLoop = NULL; @@ -289,7 +452,7 @@ PUBLIC int ApiLoopAttach(SoftMixerT *mixer, afb_req_t request, const char * uid, size_t count; case json_type_object: - newLoop = loopCreate(mixer, uid, argsJ); + newLoop = loopCreate(mixer, uid, argsJ, streamsJ); if (!newLoop) { goto fail; } @@ -304,7 +467,7 @@ PUBLIC int ApiLoopAttach(SoftMixerT *mixer, afb_req_t request, const char * uid, for (int idx = 0; idx < count; idx++) { json_object *loopJ = json_object_array_get_idx(argsJ, idx); - newLoop = loopCreate(mixer, uid, loopJ); + newLoop = loopCreate(mixer, uid, loopJ, streamsJ); if (newLoop == NULL) { goto fail_loop; } diff --git a/plugins/alsa/alsa-api-mixer.c b/plugins/alsa/alsa-api-mixer.c index e892b2f..ebb8c25 100644 --- a/plugins/alsa/alsa-api-mixer.c +++ b/plugins/alsa/alsa-api-mixer.c @@ -27,6 +27,9 @@ static struct cds_list_head mixerList; static void MixerDelete(SoftMixerT * mixer); +// AVIRT: temporary loops JSON +static json_object *LoopsJ = NULL; + static void MixerExit() { SoftMixerT *mixer, *tmp; printf("%s !\n", __func__); @@ -575,13 +578,6 @@ STATIC void MixerAttachVerb(afb_req_t request) { mixer->transaction = transaction; - AFB_API_INFO(mixer->api, "%s set LOOPS", __func__); - - if (loopsJ) { - error = ApiLoopAttach(mixer, request, uid, loopsJ); - if (error) goto fail; - } - AFB_API_INFO(mixer->api, "%s set PLAYBACKS", __func__); if (playbacksJ) { @@ -616,6 +612,16 @@ STATIC void MixerAttachVerb(afb_req_t request) { json_object_object_add(responseJ, "zone", resultJ); } + // In AVIRT mode, we require both the loops and streams JSON objects to + // construct the loopbacks, so when the loops are set, but the streams + // are not, we need to save the loops until the streams are given to us + if (streamsJ && (loopsJ || LoopsJ)) { + AFB_API_INFO(mixer->api, "%s set LOOPS/AVIRT", __func__); + error = ApiLoopAttach(mixer, request, uid, + ((loopsJ) ? loopsJ : LoopsJ), streamsJ); + if (error) goto fail_loop; + } + AFB_API_INFO(mixer->api, "%s set RAMPS", __func__); if (rampsJ) { @@ -657,12 +663,12 @@ fail_ramp: // TODO remove created ramps fail_zone: // TODO remove created zone +fail_loop: + // TODO remove created loops fail_source: // TODO remove created sources fail_sink: // TODO remove created sinks -fail_loop: - // TODO remove created loops fail: if (mixer->transaction) @@ -734,9 +740,14 @@ CTLP_CAPI(MixerAttach, source, argsJ, responseJ) { if (error) goto OnErrorExit; } - if (loopsJ) { - error = ApiLoopAttach(mixer, NULL, uid, loopsJ); + // In AVIRT mode, we require both the loops and streams JSON objects to + // construct the loopbacks, so when the loops are set, but the streams + // are not, we need to save the loops until the streams are given to us + if (loopsJ && streamsJ) { + error = ApiLoopAttach(mixer, NULL, uid, loopsJ, streamsJ); if (error) goto OnErrorExit; + } else { + LoopsJ = loopsJ; } if (zonesJ) { diff --git a/plugins/alsa/alsa-api-streams.c b/plugins/alsa/alsa-api-streams.c index c1fb749..edf506e 100644 --- a/plugins/alsa/alsa-api-streams.c +++ b/plugins/alsa/alsa-api-streams.c @@ -19,6 +19,7 @@ #define _GNU_SOURCE // needed for vasprintf #include "alsa-softmixer.h" +#include <avirt/avirt.h> #include <string.h> #include <stdbool.h> @@ -231,6 +232,7 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT char *volName = NULL; int pauseNumid = 0; int volNumid = 0; + int device, subdev; AFB_API_DEBUG(mixer->api, "NEW STREAM stream %s %s, source %s, sink %s, mute %d", @@ -238,12 +240,20 @@ STATIC int CreateOneStream(SoftMixerT *mixer, const char * uid, AlsaStreamAudioT loopDev = ApiLoopFindSubdev(mixer, stream->uid, stream->source, &loop); if (loopDev) { + if (loop->avirt) { + device = loopDev->index; + subdev = 0; + } else { // loop->avirt == false + device = loop->capture; + subdev = loopDev->index; + } + // create a valid PCM reference and try to open it. captureDev->devpath = NULL; captureDev->cardid = NULL; captureDev->cardidx = loop->sndcard->cid.cardidx; - captureDev->device = loop->capture; - captureDev->subdev = loopDev->index; + captureDev->device = device; + captureDev->subdev = subdev; captureDev->pcmplug_params = NULL; captureCard = loop->sndcard; @@ -476,11 +486,10 @@ 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) { - SOFTMIXER_NOMEM(mixer->api); - goto OnErrorExit; - } - + int device = (loop->avirt) ? captureDev->device + : loop->playback; + if (asprintf((char**) &stream->source, "hw:%d,%d,%d", captureDev->cardidx, device, capturePcm->cid.subdev) == -1) + goto OnErrorExit; } else { if (asprintf((char**) &stream->source, "hw:%d,%d,%d", captureDev->cardidx, captureDev->device, captureDev->subdev) == -1) { SOFTMIXER_NOMEM(mixer->api); diff --git a/plugins/alsa/alsa-softmixer.h b/plugins/alsa/alsa-softmixer.h index b6c0e5b..2fb9370 100644 --- a/plugins/alsa/alsa-softmixer.h +++ b/plugins/alsa/alsa-softmixer.h @@ -242,7 +242,7 @@ typedef struct { typedef struct { char * uid; - int index; + int index; // AVIRT: parent PCM index (Since subdev idx is always 0) int numid; struct cds_list_head list; } AlsaLoopSubdevT; @@ -251,10 +251,11 @@ typedef struct { struct SoftMixerT_; typedef struct AlsaSndLoopT { + bool avirt; // AVIRT: Is this loop AVIRT? const char *uid; struct SoftMixerT_ * mixer; /* owner */ - int playback; - int capture; + int playback; // AVIRT: UNUSED + int capture; // AVIRT: UNUSED AlsaSndCtlT *sndcard; int nbSubdevs; AlsaLoopSubdevT subdevs; @@ -368,7 +369,7 @@ PUBLIC AlsaPcmCtlT* AlsaCreateDmix(SoftMixerT *mixer, const char* pcmName, AlsaS // alsa-api-* -PUBLIC int ApiLoopAttach(SoftMixerT *mixer, afb_req_t request, const char *, json_object * argsJ); +PUBLIC int ApiLoopAttach(SoftMixerT *mixer, afb_req_t request, const char *, json_object * argsJ, json_object *streamsJ); PUBLIC int ApiSourceAttach(SoftMixerT *mixer, afb_req_t request, const char *, json_object * argsJ); PUBLIC int ApiSinkAttach(SoftMixerT *mixer, afb_req_t request, const char *, json_object * argsJ); PUBLIC int ApiStreamAttach(SoftMixerT *mixer, afb_req_t request, const char * uid, const char *prefix, json_object * argsJ); |