/* * 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_SOURCE // needed for vasprintf #include "alsa-softmixer.h" #include 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) cds_list_for_each_entry(pcm, &mixer->sinks.list, list) { if (pcm->nbChannels == 0) { AFB_ApiError(mixer->api, "%s: No Sink card=%s [should declare channels]", __func__, pcm->uid); goto OnErrorExit; } 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; } } } if (!found) { AFB_ApiError(mixer->api, "%s: No Channel with uid=%s [should declare channels]", __func__, uid); goto OnErrorExit; } return 0; OnErrorExit: return -1; } PUBLIC AlsaPcmCtlT* AlsaCreateRoute(SoftMixerT *mixer, AlsaSndZoneT *zone, int open) { snd_config_t *routeConfig, *elemConfig, *slaveConfig, *tableConfig, *pcmConfig; int error = 0; ChannelCardPortT slave, channelCardPort; AlsaPcmCtlT *pcmRoute = NULL; char *cardid = NULL; char *slaveUid = NULL; 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) { 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); 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. AlsaPcmChannelT * channel = cds_list_first_entry(&zone->sinks.list, AlsaPcmChannelT, list); error = CardChannelByUid(mixer, channel->uid, &slave); if (error) { AFB_ApiError(mixer->api, "%s:zone(%s) fail to find channel=%s", __func__, zone->uid, channel->uid); goto fail; } // 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(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); cds_list_for_each_entry(channel, &zone->sinks.list, list) { AFB_ApiDebug(mixer->api, "%s: zone->sink channel %s ", __func__, channel->uid); error = CardChannelByUid(mixer, channel->uid, &channelCardPort); if (error) { AFB_ApiError(mixer->api, "%s: zone(%s) fail to find channel=%s", __func__, zone->uid, channel->uid); goto fail; } 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 target = channelCardPort.port; int port = channel->port; double volume = 1.0; // currently only support 100% // 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); 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); 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; } } // update zone with route channel count and sndcard params pcmRoute->ccount = zcount; zone->ccount=zcount; // refresh global alsalib config and create PCM top config snd_config_update(); error += snd_config_top(&routeConfig); if (error) goto fail; error += snd_config_set_id(routeConfig, cardid); if (error) goto fail; error += snd_config_imake_string(&elemConfig, "type", "route"); if (error) goto fail; error += snd_config_add(routeConfig, elemConfig); if (error) goto fail; error += snd_config_make_compound(&slaveConfig, "slave", 0); 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 fail; error += snd_config_imake_integer(&elemConfig, "channels", slave.ccount); if (error) goto fail; error += snd_config_add(slaveConfig, elemConfig); if (error) goto fail; error += snd_config_add(routeConfig, slaveConfig); if (error) goto fail; error += snd_config_add(routeConfig, tableConfig); 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 fail; } snd_config_update(); error += snd_config_search(snd_config, "pcm", &pcmConfig); error += snd_config_add(pcmConfig, routeConfig); if (error) { AFB_ApiError(mixer->api, "%s: %s fail to add config route=%s error=%s", __func__, zone->uid, pcmRoute->cid.cardid, snd_strerror(error)); 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; 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(slaveUid); AFB_ApiNotice(mixer->api, "%s: zone(%s) FAIL", __func__, zone->uid); return NULL; }