summaryrefslogtreecommitdiffstats
path: root/plugins/alsa/alsa-api-mixer.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/alsa/alsa-api-mixer.c')
-rw-r--r--plugins/alsa/alsa-api-mixer.c359
1 files changed, 214 insertions, 145 deletions
diff --git a/plugins/alsa/alsa-api-mixer.c b/plugins/alsa/alsa-api-mixer.c
index b910b9f..300dd6a 100644
--- a/plugins/alsa/alsa-api-mixer.c
+++ b/plugins/alsa/alsa-api-mixer.c
@@ -23,186 +23,271 @@
#include <pthread.h>
-// Fulup need to be cleanup with new controller version
extern Lua2cWrapperT Lua2cWrap;
-// API
-
-static void MixerApiVerbCB(AFB_ReqT request) {
- json_object *valueJ, *backendJ = NULL, *frontendJ = NULL, *zonesJ = NULL, *streamsJ = NULL, *listJ = NULL;
- // retrieve action handle from request and execute the request
- json_object *argsJ = afb_request_json(request);
- json_object *responseJ = json_object_new_object();
-
- SoftMixerHandleT *mixer = (SoftMixerHandleT*) afb_request_get_vcbdata(request);
+static void MixerRemoveVerb(AFB_ReqT request) {
+ SoftMixerT *mixer = (SoftMixerT*) afb_request_get_vcbdata(request);
int error;
- int delete = 0;
-
- CtlSourceT *source = alloca(sizeof (CtlSourceT));
- source->uid = mixer->uid;
- source->api = request->dynapi;
- source->request = request;
- source->context = mixer;
-
- error = wrap_json_unpack(argsJ, "{s?b,s?o,s?o,s?o,s?o,s?o !}"
- , "delete", &delete
- , "list", &listJ
- , "backend", &backendJ
- , "frontend", &frontendJ
- , "zones", &zonesJ
- , "streams", &streamsJ
- );
- if (error) {
- AFB_ReqFailF(request, "invalid-syntax", "request missing 'uid|list|backend|frontend|zones|streams' mixer=%s", json_object_get_string(argsJ));
- goto OnErrorExit;
- }
-
- // Free attached resources and free mixer
- if (delete) {
- for (int idx; mixer->streams[idx].uid; idx++) {
- AlsaLoopStreamT *stream = &mixer->streams[idx];
-
- AFB_ApiNotice(source->api, "cleaning mixer=%s stream=%s", mixer->uid, stream->uid);
-
- error = pthread_cancel(stream->copy.thread);
- if (error) {
- AFB_ReqFailF(request, "internal-error", "Fail to kill audio-stream threads mixer=%s", mixer->uid);
- goto OnErrorExit;
- }
-
- char apiStreamVerb[128];
- error = snprintf(apiStreamVerb, sizeof (apiStreamVerb), "%s/%s", mixer->uid, stream->uid);
- if (error == sizeof (apiStreamVerb)) {
- AFB_ApiError(source->api, "LoopStreams mixer=%s fail to Registry Stream API too long %s/%s", mixer->uid, mixer->uid, stream->uid);
- goto OnErrorExit;
- }
- error = afb_dynapi_sub_verb(source->api, apiStreamVerb);
- if (error) {
- AFB_ApiError(source->api, "fail to Clean API verb=%s", apiStreamVerb);
- goto OnErrorExit;
- }
+ for (int idx; mixer->streams[idx]->uid; idx++) {
+ AlsaStreamAudioT *stream = mixer->streams[idx];
- // free audio-stream dynamic structures
- snd_pcm_close(mixer->streams[idx].copy.pcmIn);
- snd_pcm_close(mixer->streams[idx].copy.pcmOut);
- if (stream->copy.evtsrc) sd_event_source_unref(stream->copy.evtsrc);
- if (stream->copy.sdLoop) sd_event_unref(stream->copy.sdLoop);
+ AFB_ApiNotice(mixer->api, "cleaning mixer=%s stream=%s", mixer->uid, stream->uid);
+ error = pthread_cancel(stream->copy->thread);
+ if (error) {
+ AFB_ReqFailF(request, "internal-error", "Fail to kill audio-stream threads mixer=%s", mixer->uid);
+ goto OnErrorExit;
}
- // registry is attached to frontend
- if (mixer->frontend->registry)free(mixer->frontend->registry);
-
- error = afb_dynapi_sub_verb(source->api, mixer->uid);
+ error = afb_dynapi_sub_verb(mixer->api, stream->uid);
if (error) {
- AFB_ApiError(source->api, "fail to Clean API verb=%s", mixer->uid);
+ AFB_ReqFailF(request, "internal-error", "Mixer=%s fail to remove verb=%s error=%s", mixer->uid, stream->uid, strerror(error));
goto OnErrorExit;
}
- // finally free mixer handle
- free(mixer);
- responseJ = json_object_new_string("Fulup: delete might not clean everything properly");
- goto OnSuccessExit;
+ // free audio-stream dynamic structures
+ snd_pcm_close(mixer->streams[idx]->copy->pcmIn);
+ snd_pcm_close(mixer->streams[idx]->copy->pcmOut);
+ if (stream->copy->evtsrc) sd_event_source_unref(stream->copy->evtsrc);
+ if (stream->copy->sdLoop) sd_event_unref(stream->copy->sdLoop);
}
- if (listJ) {
- int streams = 0, quiet = 0, backend = 0, frontend = 0, zones = 0;
+ // // (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_ReqSucess(request, NULL, "Fulup: delete might not clean everything properly");
+
+ return;
+
+OnErrorExit:
+ AFB_ReqFail(request, "internal-error", "fail to delete mixer");
+}
- error = wrap_json_unpack(listJ, "{s?b,s?b,s?b,s?b,s?b !}"
+STATIC void MixerInfoVerb(AFB_ReqT request) {
+
+ SoftMixerT *mixer = (SoftMixerT*) afb_request_get_vcbdata(request);
+ json_object *argsJ = afb_request_json(request);
+ int error, stream = 0, quiet = 0, backend = 0, source = 0, zones = 0;
+
+ if (json_object_get_type(argsJ) == json_type_null) {
+ stream = 1;
+ } else {
+ error = wrap_json_unpack(argsJ, "{s?b,s?b,s?b,s?b,s?b !}"
, "quiet", &quiet
- , "streams", &streams
+ , "stream", &stream
, "backend", &backend
- , "frontend", &frontend
+ , "source", &source
, "zones", &zones
);
if (error) {
- AFB_ReqFailF(request, "invalid-syntax", "list missing 'uid|backend|frontend|zones|streams' list=%s", json_object_get_string(listJ));
+ AFB_ReqFailF(request, "invalid-syntax", "list missing 'quiet|stream|backend|source' argsJ=%s", json_object_get_string(argsJ));
goto OnErrorExit;
}
+ }
+ json_object *responseJ = json_object_new_object();
- if (streams) {
- streamsJ = json_object_new_array();
-
- AlsaLoopStreamT *streams = mixer->streams;
- for (int idx = 0; streams[idx].uid; idx++) {
- if (quiet) {
- json_object_array_add(streamsJ, json_object_new_string(streams[idx].uid));
- } else {
- json_object *numidJ;
- wrap_json_pack(&numidJ, "{si,si}"
- , "volume", streams[idx].volume
- , "mute", streams[idx].mute
- );
- wrap_json_pack(&valueJ, "{ss,so}"
- , "uid", streams[idx].uid
- , "numid", numidJ
- );
- json_object_array_add(streamsJ, valueJ);
- AFB_ApiWarning(request->dynapi, "stream=%s", json_object_get_string(streamsJ));
- }
-
+ if (stream) {
+ json_object *streamsJ = json_object_new_array();
+ json_object *valueJ;
+
+ AlsaStreamAudioT **streams = mixer->streams;
+ for (int idx = 0; streams[idx]->uid; idx++) {
+ if (quiet) {
+ json_object_array_add(streamsJ, json_object_new_string(streams[idx]->uid));
+ } else {
+ json_object *numidJ;
+ wrap_json_pack(&numidJ, "{si,si}"
+ , "volume", streams[idx]->volume
+ , "mute", streams[idx]->mute
+ );
+ wrap_json_pack(&valueJ, "{ss,so}"
+ , "uid", streams[idx]->uid
+ , "numid", numidJ
+ );
+ json_object_array_add(streamsJ, valueJ);
+ AFB_ApiWarning(request->dynapi, "stream=%s", json_object_get_string(streamsJ));
}
- json_object_object_add(responseJ, "streams", streamsJ);
- }
- if (backend || frontend || zones) {
- AFB_ReqFailF(request, "not implemented", "(Fulup) list action Still To Be Done");
- goto OnErrorExit;
}
+ json_object_object_add(responseJ, "streams", streamsJ);
+ }
+
+ if (backend || source || zones) {
+ AFB_ReqFailF(request, "not implemented", "(Fulup) list action Still To Be Done");
+ goto OnErrorExit;
+ }
+
+ AFB_ReqSucess(request, responseJ, NULL);
+ return;
+
+OnErrorExit:
+ AFB_ReqFail(request, "internal-error", "fail to get mixer info");
+
+}
+
+STATIC void MixerAttachVerb(AFB_ReqT request) {
+ SoftMixerT *mixer = (SoftMixerT*) afb_request_get_vcbdata(request);
+ const char *uid=NULL;
+ json_object *playbackJ = NULL, *captureJ = NULL, *zonesJ = NULL, *streamsJ = NULL, *rampsJ = NULL, *loopsJ = NULL;
+ json_object *argsJ = afb_request_json(request);
+ json_object *responseJ;
+ int error;
+
+ error = wrap_json_unpack(argsJ, "{ss,s?o,s?o,s?o,s?o,s?o,s?o !}"
+ , "uid", &uid
+ , "ramps", &rampsJ
+ , "playbacks", &playbackJ
+ , "captures", &captureJ
+ , "loops", &loopsJ
+ , "zones", &zonesJ
+ , "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_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;
+ }
- AFB_ReqSucess(request, responseJ, NULL);
- return;
+ if (playbackJ) {
+ error = ApiSinkAttach(mixer, request, uid, playbackJ);
+ if (error) goto OnErrorExit;
}
- if (backendJ) {
- error = SndBackend(source, backendJ);
+ if (captureJ) {
+ error = ApiSourceAttach(mixer, request, uid, captureJ);
if (error) goto OnErrorExit;
}
- if (frontendJ) {
- error = SndFrontend(source, frontendJ);
+ if (loopsJ) {
+ error = ApiLoopAttach(mixer, request, uid, loopsJ);
if (error) goto OnErrorExit;
}
if (zonesJ) {
- error = SndZones(source, zonesJ);
+ error = ApiZoneAttach(mixer, request, uid, zonesJ);
+ if (error) goto OnErrorExit;
+ }
+
+ if (rampsJ) {
+ error = ApiRampAttach(mixer, request, uid, rampsJ);
if (error) goto OnErrorExit;
}
if (streamsJ) {
- error = LoopStreams(source, streamsJ, &responseJ);
+ error = ApiStreamAttach(mixer, request, uid, streamsJ, &responseJ);
if (error) goto OnErrorExit;
}
-OnSuccessExit:
- AFB_ReqSucess(request, responseJ, mixer->uid);
+ AFB_ReqNotice(request, "**** mixer=%s response=%s", json_object_get_string(responseJ), mixer->uid);
+ AFB_ReqSucess(request, NULL, mixer->uid);
return;
OnErrorExit:
return;
}
+STATIC void MixerPingVerb(AFB_ReqT request) {
+ static int count = 0;
+ count++;
+ AFB_ReqNotice(request, "Controller:ping count=%d", count);
+ AFB_ReqSucess(request, json_object_new_int(count), NULL);
+
+ return;
+}
+
+STATIC void MixerEventCB(AFB_ApiT api, const char *evtLabel, struct json_object *eventJ) {
+
+ SoftMixerT *mixer = (SoftMixerT*) afb_dynapi_get_userdata(api);
+ assert(mixer);
+
+ AFB_ApiNotice(api, "Mixer=%s Received event=%s, eventJ=%s", mixer->uid, evtLabel, json_object_get_string(eventJ));
+}
+
+// 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 = "ping", .callback = MixerPingVerb, .info = "ping count test"},
+ { .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 = NULL} /* marker for end of the array */
+};
+
+STATIC int LoadStaticVerbs(SoftMixerT *mixer, AFB_ApiVerbs *verbs) {
+ int errcount = 0;
+
+ for (int idx = 0; verbs[idx].verb; idx++) {
+ errcount += afb_dynapi_add_verb(mixer->api, CtrlApiVerbs[idx].verb, CtrlApiVerbs[idx].info, CtrlApiVerbs[idx].callback, (void*) mixer, CtrlApiVerbs[idx].auth, 0);
+ }
+
+ return errcount;
+};
+
+STATIC int MixerInitCB(AFB_ApiT api) {
+
+ SoftMixerT *mixer = (SoftMixerT*) afb_dynapi_get_userdata(api);
+ assert(mixer);
+
+ // attach AFB mainloop to mixer
+ mixer->sdLoop = AFB_GetEventLoop(api);
+
+
+ AFB_ApiNotice(api, "MixerInitCB API=%s activated info=%s", mixer->uid, mixer->info);
+
+ return 0;
+}
+
+STATIC int MixerApiCB(void* handle, AFB_ApiT api) {
+ SoftMixerT *mixer = (SoftMixerT*) handle;
+
+ mixer->api= api;
+ afb_dynapi_set_userdata(api, mixer);
+ afb_dynapi_on_event(api, MixerEventCB);
+ afb_dynapi_on_init(api, MixerInitCB);
+
+ int error = LoadStaticVerbs(mixer, CtrlApiVerbs);
+ if (error) goto OnErrorExit;
+
+ return 0;
+
+OnErrorExit:
+ return -1;
+}
+
CTLP_LUA2C(_mixer_new_, source, argsJ, responseJ) {
- SoftMixerHandleT *mixer = calloc(1, sizeof (SoftMixerHandleT));
- json_object *backendJ = NULL, *frontendJ = NULL, *zonesJ = NULL, *streamsJ = NULL;
+ SoftMixerT *mixer = calloc(1, sizeof (SoftMixerT));
int error;
- assert(source->api);
+ mixer->max.loops = SMIXER_DEFLT_RAMPS;
+ mixer->max.sinks = SMIXER_DEFLT_SINKS;
+ mixer->max.sources = SMIXER_DEFLT_SOURCES;
+ mixer->max.zones = SMIXER_DEFLT_ZONES;
+ mixer->max.streams = SMIXER_DEFLT_STREAMS;
+ 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;
}
- error = wrap_json_unpack(argsJ, "{ss,s?s,s?o,s?o,s?o,s?o !}"
+ error = wrap_json_unpack(argsJ, "{ss,s?s,s?i,s?i,s?i,s?i,s?i,s?i !}"
, "uid", &mixer->uid
, "info", &mixer->info
- , "backend", &backendJ
- , "frontend", &frontendJ
- , "zones", &zonesJ
- , "streams", &streamsJ);
+ , "max_loop", &mixer->max.loops
+ , "max_sink", &mixer->max.sinks
+ , "max_source", &mixer->max.sources
+ , "max_zone", &mixer->max.zones
+ , "max_stream", &mixer->max.streams
+ , "max_ramp", &mixer->max.ramps
+ );
if (error) {
- AFB_ApiNotice(source->api, "_mixer_new_ missing 'uid|backend|frontend|zones|streams' mixer=%s", json_object_get_string(argsJ));
+ 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;
}
@@ -210,34 +295,18 @@ CTLP_LUA2C(_mixer_new_, source, argsJ, responseJ) {
mixer->uid = strdup(mixer->uid);
if (mixer->info)mixer->info = strdup(mixer->info);
+ mixer->loops = calloc(mixer->max.loops+1, sizeof (AlsaSndLoopT));
+ mixer->sinks = calloc(mixer->max.sinks+1, sizeof (AlsaSndPcmT));
+ mixer->sources = calloc(mixer->max.sources+1, sizeof (AlsaSndPcmT));
+ mixer->zones = calloc(mixer->max.zones+1, sizeof (AlsaSndZoneT));
+ mixer->streams = calloc(mixer->max.streams+1, sizeof (AlsaStreamAudioT));
+ mixer->ramps = calloc(mixer->max.ramps+1, sizeof (AlsaVolRampT));
+
// create mixer verb within API.
- error = afb_dynapi_add_verb(source->api, mixer->uid, mixer->info, MixerApiVerbCB, mixer, NULL, 0);
+ error = afb_dynapi_new_api(source->api, mixer->uid, mixer->info, !MAINLOOP_CONCURENCY, MixerApiCB, mixer);
if (error) {
AFB_ApiError(source->api, "_mixer_new_ mixer=%s fail to Registry API verb", mixer->uid);
- return -1;
- }
-
- // make sure sub command get access to mixer handle
- source->context = mixer;
-
- if (backendJ) {
- error = SndBackend(source, backendJ);
- if (error) goto OnErrorExit;
- }
-
- if (frontendJ) {
- error = SndFrontend(source, frontendJ);
- if (error) goto OnErrorExit;
- }
-
- if (zonesJ) {
- error = SndZones(source, zonesJ);
- if (error) goto OnErrorExit;
- }
-
- if (streamsJ) {
- error = LoopStreams(source, streamsJ, responseJ);
- if (error) goto OnErrorExit;
+ goto OnErrorExit;
}
return 0;