/* * Copyright (C) 2018 "IoT.bzh" * Author Fulup Ar Foll * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #define _GNU_MIXER // needed for vasprintf #include "alsa-softmixer.h" #include // Fulup need to be cleanup with new controller version extern Lua2cWrapperT Lua2cWrap; PUBLIC AlsaSndZoneT *ApiZoneGetByUid(SoftMixerT *mixer, const char *target) { AlsaSndZoneT * zone = NULL; if (mixer->nbZones == 0) { AFB_API_ERROR(mixer->api, "%s mixer=%s does not have any zone", __func__, mixer->uid); goto fail; } // search for subdev into every registered loop cds_list_for_each_entry(zone, &mixer->zones.list, list) { if (zone->uid && !strcasecmp(zone->uid, target)) { return zone; } } fail: AFB_API_ERROR(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); 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 fail; channel->uid = strdup(channel->uid); if (!channel->uid) { SOFTMIXER_NOMEM(mixer->api); goto fail_channel; } AFB_API_DEBUG(mixer->api, "%s: uid %s, channel uid %s, port %d, volume %f", __func__, uid, channel->uid, channel->port, channel->volume); return channel; fail_channel: free(channel); fail: AFB_API_ERROR(mixer->api, "%s: zone=%s channel: missing (target|channel) json=%s", __func__, uid, json_object_get_string(channelJ)); return NULL; } STATIC AlsaSndZoneT *AttachOneZone(SoftMixerT *mixer, const char *uid, json_object *zoneJ) { AFB_API_DEBUG(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_API_NOTICE(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: 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); for (int idx = 0; idx < count; idx++) { json_object *subdevJ = json_object_array_get_idx(sinkJ, idx); 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_API_ERROR(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: 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); for (int idx = 0; idx < count; idx++) { json_object *subdevJ = json_object_array_get_idx(sourceJ, idx); channel = ProcessOneChannel(mixer, zone->uid, subdevJ); if (!zone) goto fail_uid; zone->nbSources++; cds_list_add(&channel->list, &zone->sources.list); } break; default: AFB_API_ERROR(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_API_DEBUG(mixer->api, "%s uid %s DONE", __func__, uid); return zone; fail_uid: free((char*)zone->uid); fail_zone: free(zone); fail: return NULL; } static void zoneDestroy(SoftMixerT* mixer, void * arg) { AlsaSndZoneT * zone = (AlsaSndZoneT*) arg; AFB_API_DEBUG(mixer->api, "%s... %s (%d sinks, %d sources)", __func__, zone->uid, zone->nbSinks, zone->nbSources); if (zone->routeConfig) { AFB_API_DEBUG(mixer->api, "%s... %s delete route config", __func__, zone->uid); snd_config_delete(zone->routeConfig); snd_config_update(); zone->routeConfig = NULL; } AlsaPcmChannelT * channel, *tmp; 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_API_DEBUG(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_API_ERROR(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; zone->quirks = routeConfig->quirks; fail: return zone; } PUBLIC int ApiZoneAttach(SoftMixerT *mixer, afb_req_t request, const char * uid, json_object * argsJ) { AFB_API_DEBUG(mixer->api, "%s uid %s", __func__, uid); if (mixer->nbZones >= mixer->max.zones) { afb_req_fail_f(request, "too-small", "mixer=%s max zone=%d", mixer->uid, mixer->max.zones); goto OnErrorExit; } switch (json_object_get_type(argsJ)) { long count; case json_type_object: { AlsaSndZoneT * newZone = zoneCreate(mixer, uid, argsJ); if (!newZone) { afb_req_fail_f(request, "bad-zone", "mixer=%s invalid zone= %s", mixer->uid, json_object_get_string(argsJ)); goto OnErrorExit; } break; } case json_type_array: count = json_object_array_length(argsJ); if (count > (mixer->max.zones - mixer->nbZones)) { afb_req_fail_f(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 * newZone = zoneCreate(mixer, uid, zoneJ); if (!newZone) { afb_req_fail_f(request, "bad-zone", "mixer=%s invalid zone= %s", mixer->uid, json_object_get_string(zoneJ)); goto OnErrorExit; } } break; default: afb_req_fail_f(request, "invalid-syntax", "mixer=%s zones invalid argsJ= %s", mixer->uid, json_object_get_string(argsJ)); goto OnErrorExit; } AFB_API_DEBUG(mixer->api, "%s uid %s DONE", __func__, uid); return 0; OnErrorExit: return -1; }