diff options
Diffstat (limited to 'plugins/alsa/alsa-api-mixer.c')
-rw-r--r-- | plugins/alsa/alsa-api-mixer.c | 359 |
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; |