/* * Copyright (C) 2018 "IoT.bzh" * Author Fulup Ar Foll <fulup@iot.bzh> * * 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 <stdbool.h> ALSA_PLUG_PROTO(route); typedef struct { const char *uid; const char *cardid; int cardidx; int ccount; int port; } ChannelCardPortT; STATIC int CardChannelByUid(SoftMixerT *mixer, const char *uid, ChannelCardPortT *response) { bool found = false; // search for channel within all sound card sink (channel port target is computed by order) for (int idx = 0; mixer->sinks[idx]; idx++) { int jdx; AlsaSndPcmT * pcm = mixer->sinks[idx]; AlsaPcmChannelT **channels = pcm->channels; if (!channels) { AFB_ApiError(mixer->api, "%s: No Sink card=%s [should declare channels]", __func__, pcm->uid); goto OnErrorExit; } for (jdx = 0; jdx < pcm->ccount; jdx++) { if (!strcasecmp(channels[jdx]->uid, uid)) { response->port = channels[jdx]->port; response->uid = pcm->uid; response->ccount = pcm->ccount; response->cardid = pcm->sndcard->cid.cardid; response->cardidx = pcm->sndcard->cid.cardidx; 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 scount=0, error = 0; ChannelCardPortT slave, channel; AlsaPcmCtlT *pcmRoute = calloc(1, sizeof (AlsaPcmCtlT)); char *cardid = NULL; char *dmixUid = NULL; if (!mixer->sinks) { AFB_ApiError(mixer->api, "AlsaCreateRoute: mixer=%s zone(%s)(zone) No sink found [should Registry sound card first]", mixer->uid, zone->uid); goto OnErrorExit; } if (asprintf(&cardid, "route-%s", zone->uid) == -1) goto OnErrorExit; pcmRoute->cid.cardid = (const char *) cardid; pcmRoute->params = ApiSinkGetParamsByZone(mixer, zone->uid); zone->params=pcmRoute->params; // use 1st zone channel to retrieve sound card name + channel count. error = CardChannelByUid(mixer, zone->sinks[0]->uid, &slave); if (error) { AFB_ApiError(mixer->api, "AlsaCreateRoute:zone(%s) fail to find channel=%s", zone->uid, zone->sinks[0]->uid); goto OnErrorExit; } // move from hardware to DMIX attach to sndcard if (asprintf(&dmixUid, "dmix-%s", slave.uid) == -1) goto OnErrorExit; // temporary store to unable multiple channel to route to the same port snd_config_t **cports = alloca(slave.ccount * sizeof (void*)); memset(cports, 0, slave.ccount * sizeof (void*)); int zcount = 0; // We create 1st ttable to retrieve sndcard slave and channel count (void)snd_config_make_compound(&tableConfig, "ttable", 0); for (scount = 0; zone->sinks[scount] != NULL; scount++) { error = CardChannelByUid(mixer, zone->sinks[scount]->uid, &channel); if (error) { AFB_ApiError(mixer->api, "AlsaCreateRoute:zone(%s) fail to find channel=%s", zone->uid, zone->sinks[scount]->uid); goto OnErrorExit; } if (slave.cardidx != channel.cardidx) { AFB_ApiError(mixer->api, "AlsaCreateRoute:zone(%s) cannot span over multiple sound card %s != %s ", zone->uid, slave.cardid, channel.cardid); goto OnErrorExit; } int port = zone->sinks[scount]->port; int target = channel.port; double volume = 1.0; // currently only support 100% // if channel entry does not exit into ttable create it now if (!cports[port]) { zcount++; 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); error += snd_config_add(tableConfig, cports[port]); } // ttable require target port as a table and volume as a value char targetS[4]; snprintf(targetS, sizeof (targetS), "%d", target); error += snd_config_imake_real(&elemConfig, targetS, volume); error += snd_config_add(cports[port], elemConfig); if (error) goto OnErrorExit; } if (error) goto OnErrorExit; // 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 OnErrorExit; error += snd_config_set_id(routeConfig, cardid); if (error) goto OnErrorExit; error += snd_config_imake_string(&elemConfig, "type", "route"); if (error) goto OnErrorExit; error += snd_config_add(routeConfig, elemConfig); if (error) goto OnErrorExit; error += snd_config_make_compound(&slaveConfig, "slave", 0); if (error) goto OnErrorExit; error += snd_config_imake_string(&elemConfig, "pcm", dmixUid); if (error) goto OnErrorExit; error += snd_config_add(slaveConfig, elemConfig); if (error) goto OnErrorExit; error += snd_config_imake_integer(&elemConfig, "channels", slave.ccount); if (error) goto OnErrorExit; error += snd_config_add(slaveConfig, elemConfig); if (error) goto OnErrorExit; error += snd_config_add(routeConfig, slaveConfig); if (error) goto OnErrorExit; error += snd_config_add(routeConfig, tableConfig); if (error) goto OnErrorExit; 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 OnErrorExit; } 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 OnErrorExit; } // Debug config & pcm AFB_ApiNotice(mixer->api, "%s: zone(%s) DONE", __func__, zone->uid); AlsaDumpCtlConfig(mixer, "plug-route", routeConfig, 1); return pcmRoute; OnErrorExit: free(pcmRoute); free(cardid); free(dmixUid); AlsaDumpCtlConfig(mixer, "plug-pcm", snd_config, 1); AlsaDumpCtlConfig(mixer, "plug-route", routeConfig, 1); AFB_ApiNotice(mixer->api, "%s: zone(%s) FAIL", __func__, zone->uid); return NULL; }