summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--conf.d/project/etc/4a-softmixer-config.json10
-rw-r--r--conf.d/project/htdocs/AudioBinding.js6
-rw-r--r--conf.d/project/lua.d/README.md60
-rw-r--r--conf.d/project/lua.d/softmixer-01.lua181
-rw-r--r--nbproject/configurations.xml14
-rw-r--r--plugins/alsa/alsa-api-sndcards.c166
-rw-r--r--plugins/alsa/alsa-api-sndloops.c135
-rw-r--r--plugins/alsa/alsa-api-sndstreams.c161
-rw-r--r--plugins/alsa/alsa-api-sndzones.c145
-rw-r--r--plugins/alsa/alsa-core-ctl.c40
-rw-r--r--plugins/alsa/alsa-core-pcm.c20
-rw-r--r--plugins/alsa/alsa-plug-dmix.c29
-rw-r--r--plugins/alsa/alsa-plug-multi.c105
-rw-r--r--plugins/alsa/alsa-plug-route.c140
-rw-r--r--plugins/alsa/alsa-plug-stream.c121
-rw-r--r--plugins/alsa/alsa-plug-vol.c77
-rw-r--r--plugins/alsa/alsa-softmixer.c93
-rw-r--r--plugins/alsa/alsa-softmixer.h96
-rw-r--r--plugins/alsa/alsa-utils-bypath.c65
-rw-r--r--plugins/alsa/alsa-utils-dump.c110
20 files changed, 1492 insertions, 282 deletions
diff --git a/conf.d/project/etc/4a-softmixer-config.json b/conf.d/project/etc/4a-softmixer-config.json
index e66a2e6..b094f2f 100644
--- a/conf.d/project/etc/4a-softmixer-config.json
+++ b/conf.d/project/etc/4a-softmixer-config.json
@@ -10,7 +10,7 @@
{
"uid": "alsa-router",
"ldpath": "./plugins/alsa",
- "lua2c": ["AlsaDmix", "AlsaRouter"],
+ "lua2c": ["snd_cards", "snd_zones", "snd_loops", "snd_streams"],
"info": "Map alsa-loop subdevices to 4A HAL streams"
}
],
@@ -83,17 +83,17 @@
"lua": "_mixer_config_"
},
{
- "uid": "stream",
+ "uid": "snd-cards",
"callback": {
"plugin": "alsa-router",
- "function": "stream_ctl"
+ "function": "snd_cards"
}
},
{
- "uid": "zone",
+ "uid": "snd-zone",
"callback": {
"plugin": "alsa-router",
- "function": "zone_ctl"
+ "function": "snd_zones"
}
}
]
diff --git a/conf.d/project/htdocs/AudioBinding.js b/conf.d/project/htdocs/AudioBinding.js
index 0f5caf9..1e539c2 100644
--- a/conf.d/project/htdocs/AudioBinding.js
+++ b/conf.d/project/htdocs/AudioBinding.js
@@ -39,8 +39,8 @@
}
// default soundcard is "PCH"
- var devid=getParameterByName("devid");
- if (!devid) devid="hw:1";
+ var cardid=getParameterByName("cardid");
+ if (!cardid) cardid="hw:1";
var haldev=getParameterByName("haldev");
if (!haldev) haldev="scarlett-usb";
@@ -115,7 +115,7 @@
// Alsa LowLevel selection mode
if (response[idx].name) opt.text = response[idx].name;
- if (response[idx].devid) opt.value = response[idx].devid;
+ if (response[idx].cardid) opt.value = response[idx].cardid;
// HAL selection mode
if (response[idx].shortname) opt.text = response[idx].shortname;
diff --git a/conf.d/project/lua.d/README.md b/conf.d/project/lua.d/README.md
new file mode 100644
index 0000000..11dbd49
--- /dev/null
+++ b/conf.d/project/lua.d/README.md
@@ -0,0 +1,60 @@
+Softmixer controller for 4A (AGL Advance Audio Architecture).
+------------------------------------------------------------
+
+ * Object: LUA API Documentation
+ * Status: In Progress
+ * Author: Fulup Ar Foll fulup@iot.bzh
+ * Date : April-2018
+
+## Sound Cards
+
+User may define as many sound card as needed. All sound cards will be group within one global multi channels card.
+As a result, declaring three USB stereo sound card is equivalent to declare one Renesas GEN3 with 6 channels
+
+```
+ -- Sound Card Definition
+ -- ==========================
+ -- local sndcard_sample = {
+ --
+ -- * Mandatory UID will be the ALSA PCM
+ -- ["uid"]= "YAMAHA-APU70",
+ --
+ -- * Mandatory Card might be found by either its devpath, its cardid or cardindex (should provide only one)
+ -- ["devpath"]= "/dev/snd/by-xxx/xxxx, (any path in /dev/snd pointing to a valid sndcard control works
+ -- ["cardid"] = "hw:xx", [xx] may either be a card index or a name (eg: hw:USB)
+ -- ["cardidx"] = N,
+ --
+ -- * Optional Device and subdev
+ -- ["device"] = N, (default 0)
+ -- ["subdev"] = N, (default 0)
+ --
+ -- * Mandotory List of sink channels attached to the card
+ -- ["sink"] = {
+ -- [0]= {["uid"]= "front-right", ["port"]= 0},
+ -- [1]= {["uid"]= "front-left", ["port"]= 1},
+ -- }
+ -- }
+```
+
+Sound card should be group in a table in order to request grouping as a multi channel PCM. Note that this grouping
+relies on ALSA 'multi' plugin and thus inherit of its limits.
+
+```
+ local sndcards= {
+ [0] = sndcard_0,
+ [1] = sndcard_2,
+ [3] = sndcard_3,
+ }
+```
+
+Call
+
+```
+ error= L2C:snd_cards (source, sndcards)
+ if (error ~= 0) then
+ AFB:error (source, "--InLua-- L2C:sndcards fail to attach sndcards=%s", Dump_Table(sndcards))
+ --goto OnErrorExit
+ else
+ AFB:notice (source, "--InLua-- L2C:sndcards done response=%s", Dump_Table(response))
+ end
+``` \ No newline at end of file
diff --git a/conf.d/project/lua.d/softmixer-01.lua b/conf.d/project/lua.d/softmixer-01.lua
index 8858b93..8b586a7 100644
--- a/conf.d/project/lua.d/softmixer-01.lua
+++ b/conf.d/project/lua.d/softmixer-01.lua
@@ -31,34 +31,179 @@ end
-- Display receive arguments and echo them to caller
function _mixer_config_ (source, args)
+ do
+ local error
+
+ -- ============================= Sound Cards ===================
+
+ local snd_params = {
+ ["rate"] = 48000,
+ ["channel"]= 2,
+ }
+
+ local sndcard_0 = {
+ ["uid"]= "YAMAHA-APU70",
+ ["devpath"]= "/dev/snd/by-id/usb-YAMAHA_Corporation_YAMAHA_AP-U70_USB_Audio_00-00",
+ ["params"] = params,
+ ["sink"] = {
+ [0]= {["uid"]= "front-right", ["port"]= 0},
+ [1]= {["uid"]= "front-left", ["port"]= 1},
+ }
+ }
- local devin = {
- ["path"]= "/dev/snd/by-path/platform-snd_aloop.0",
- ["dev"]= 1,
- ["sub"]= 0,
- ["numid"]= 51,
+ local sndcard_1 = {
+ ["uid"]= "Jabra-Solemate",
+ ["devpath"]= "/dev/snd/by-id/usb-0b0e_Jabra_SOLEMATE_v1.34.0-00",
+ ["params"] = params,
+ ["sink"] = {
+ [0]= {["uid"]= "front-right", ["port"]= 0},
+ [1]= {["uid"]= "front-left", ["port"]= 1},
+ }
}
- local devout = {
- ["path"]= "/dev/snd/by-id/usb-YAMAHA_Corporation_YAMAHA_AP-U70_USB_Audio_00-00",
- -- ["path"]= "/dev/snd/by-id/usb-0b0e_Jabra_SOLEMATE_v1.34.0-00",
- ["dev"]= 0,
- ["sub"]= 0,
+ local sndcard_2 = {
+ ["uid"]= "Jabra-410",
+ ["params"] = params,
+ ["devpath"]= "/dev/snd/by-id/usb-0b0e_Jabra_SPEAK_410_USB_745C4B15BD11x010900-00",
+ ["sink"] = {
+ [0]= {["uid"]= "back-right", ["port"]= 0},
+ [1]= {["uid"]= "back-left", ["port"]= 1},
+ }
+ }
+
+ -- group sound card as one multi channels card
+ local sndcards= {
+ sndcard_0,
+ sndcard_2,
}
- local params = {
- ["rate"]= 48000,
- --["rate"]= 44100,
- ["channels"]= 2,
+ error= L2C:snd_cards (source, sndcards)
+ if (error ~= 0) then
+ AFB:error (source, "--InLua-- L2C:snd_cards fail to attach sndcards=%s", Dump_Table(sndcards))
+ goto OnErrorExit
+ else
+ AFB:notice (source, "--InLua-- L2C:snd_cards done response=%s\n", Dump_Table(response))
+ end
+
+ -- ============================= Zones ===================
+
+ local zone_front= {
+ ["uid"] = "front-seats",
+ ["type"] = "playback",
+ ["mapping"] = {
+ {["target"]="front-right",["channel"]=0},
+ {["target"]="front-left" ,["channel"]=1},
+ }
}
- -- Call AlsaSoftRouter
- L2C:alsarouter(source, {["devin"]= devin, ["devout"]= devout, ["params"]= params})
+ local zone_back= {
+ ["uid"] = "back-seats",
+ ["type"] = "playback",
+ ["mapping"] = {
+ {["target"]="back-right",["channel"]=0},
+ {["target"]="back-left" ,["channel"]=1},
+ }
+ }
+
+ local zone_all= {
+ ["uid"] = "all-seats",
+ ["type"] = "playback",
+ ["mapping"] = {
+ {["target"]="front-right",["channel"]=0},
+ {["target"]="front-left" ,["channel"]=1},
+ {["target"]="back-right" ,["channel"]=0},
+ {["target"]="back-left" ,["channel"]=1},
+ }
+ }
+
+ local multi_zones = {
+ zone_all,
+ zone_front,
+ zone_back,
+ }
+
+ error= L2C:snd_zones (source, multi_zones)
+ if (error ~= 0) then
+ AFB:error (source, "--InLua-- L2C:snd_zones fail to attach sndcards=%s", Dump_Table(multi_zones))
+ goto OnErrorExit
+ else
+ AFB:notice (source, "--InLua-- L2C:snd_zones done response=%s\n", Dump_Table(response))
+ end
+
+ -- ======================= Loop PCM ===========================
+
+ local snd_aloop = {
+ ["uid"] = "Alsa-Loop",
+ ["devpath"] = "/dev/snd/by-path/platform-snd_aloop.0",
+ ["devices"] = {["playback"]=0,["capture"]=1},
+ ["subdevs"] = {
+ {["subdev"]= 0, ["numid"]= 51},
+ {["subdev"]= 1, ["numid"]= 57},
+ {["subdev"]= 2, ["numid"]= 63},
+ {["subdev"]= 3, ["numid"]= 69},
+ {["subdev"]= 4, ["numid"]= 75},
+ {["subdev"]= 5, ["numid"]= 81},
+ {["subdev"]= 6, ["numid"]= 87},
+ {["subdev"]= 7, ["numid"]= 93},
+ }
+ }
+ error= L2C:snd_loops (source, snd_aloop)
+ if (error ~= 0) then
+ AFB:error (source, "--InLua-- L2C:snd_loops fail to attach sndcards=%s", Dump_Table(aloop))
+ goto OnErrorExit
+ else
+ AFB:notice (source, "--InLua-- L2C:snd_loops done response=%s\n", Dump_Table(response))
+ end
+
+ -- =================== Audio Stream ============================
+
+ local stream_music= {
+ ["uid"] = "multimedia",
+ ["zone"] = "all-seats",
+ ["volume"]= 70,
+ ["rate"] = 48000,
+ ["mute"] = false;
+ }
+
+ local stream_navigation= {
+ ["uid"] = "navigation",
+ ["zone"] = "front-seats",
+ ["volume"]= 80,
+ ["mute"] = false;
+ }
+
+ local stream_children= {
+ ["uid"] = "children",
+ ["zone"] = "back-seats",
+ ["volume"]= 50,
+ ["mute"] = false;
+ }
+
+ local snd_streams = {
+ stream_music,
+ stream_navigation,
+ stream_children,
+ }
+
+ error= L2C:snd_streams (source, snd_streams)
+ if (error ~= 0) then
+ AFB:error (source, "--InLua-- L2C:snd_streams fail to attach sndcards=%s", Dump_Table(aloop))
+ goto OnErrorExit
+ else
+ AFB:notice (source, "--InLua-- L2C:streams_loops done response=%s\n", Dump_Table(response))
+ end
+
+
+ -- ================== Happy End =============================
AFB:notice (source, "--InLua-- _mixer_config_ done")
+ return 0 end
- return 0 -- happy end
-end
+ -- ================= Unhappy End ============================
+ ::OnErrorExit::
+ AFB:error (source, "--InLua-- snd_attach fail")
+ return 1 -- unhappy end --
+end
-- Display receive arguments and echo them to caller
function _init_softmixer_ (source, args)
diff --git a/nbproject/configurations.xml b/nbproject/configurations.xml
index 95c71af..210a225 100644
--- a/nbproject/configurations.xml
+++ b/nbproject/configurations.xml
@@ -26,7 +26,7 @@
<in>alsa-capture.c</in>
<in>alsa-core-ctl.c</in>
<in>alsa-core-pcm.c</in>
- <in>alsa-plug-vol.c</in>
+ <in>alsa-plug-stream.c</in>
<in>alsa-softmixer.c</in>
<in>alsa-utils-bypath.c</in>
<in>alsa-utils-dump.c</in>
@@ -59,6 +59,9 @@
</flagsDictionary>
<codeAssistance>
<includeAdditional>true</includeAdditional>
+ <transientMacros>
+ <Elem>size_t=unsigned long</Elem>
+ </transientMacros>
</codeAssistance>
<makefileType>
<makeTool>
@@ -81,6 +84,7 @@
<incDir>
<pElem>../../../opt/include/afb</pElem>
<pElem>app-afb-helpers-submodule</pElem>
+ <pElem>/usr/lib64/gcc/x86_64-suse-linux/7/include</pElem>
<pElem>../../../opt/include</pElem>
<pElem>build/app-afb-helpers-submodule</pElem>
</incDir>
@@ -93,6 +97,7 @@
<cTool flags="1">
<incDir>
<pElem>app-afb-helpers-submodule</pElem>
+ <pElem>/usr/lib64/gcc/x86_64-suse-linux/7/include</pElem>
<pElem>build/app-afb-helpers-submodule</pElem>
</incDir>
</cTool>
@@ -158,7 +163,9 @@
<incDir>
<pElem>../../../opt/include/afb</pElem>
<pElem>app-controller-submodule/ctl-lib</pElem>
+ <pElem>/usr/lib64/gcc/x86_64-suse-linux/7/include</pElem>
<pElem>app-afb-helpers-submodule</pElem>
+ <pElem>/usr/include/lua5.3</pElem>
<pElem>../../../opt/include</pElem>
<pElem>build/app-controller-submodule/ctl-lib</pElem>
</incDir>
@@ -169,6 +176,7 @@
<incDir>
<pElem>../../../opt/include/afb</pElem>
<pElem>mixer-binding</pElem>
+ <pElem>/usr/lib64/gcc/x86_64-suse-linux/7/include</pElem>
<pElem>../../../opt/include/alsa</pElem>
<pElem>app-afb-helpers-submodule</pElem>
<pElem>app-controller-submodule/ctl-lib</pElem>
@@ -180,7 +188,9 @@
<folder path="0/plugins">
<cTool>
<incDir>
+ <pElem>/usr/include</pElem>
<pElem>/usr/include/json-c</pElem>
+ <pElem>/usr/include/alsa</pElem>
<pElem>../../../opt/include</pElem>
<pElem>/usr/include/p11-kit-1</pElem>
<pElem>/usr/include/uuid</pElem>
@@ -218,7 +228,7 @@
<cTool flags="0">
</cTool>
</item>
- <item path="plugins/alsa/alsa-plug-vol.c" ex="false" tool="0" flavor2="2">
+ <item path="plugins/alsa/alsa-plug-stream.c" ex="false" tool="0" flavor2="2">
<cTool flags="0">
</cTool>
</item>
diff --git a/plugins/alsa/alsa-api-sndcards.c b/plugins/alsa/alsa-api-sndcards.c
new file mode 100644
index 0000000..d48abd8
--- /dev/null
+++ b/plugins/alsa/alsa-api-sndcards.c
@@ -0,0 +1,166 @@
+/*
+ * 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"
+
+// Fulup need to be cleanup with new controller version
+extern Lua2cWrapperT Lua2cWrap;
+
+STATIC int ProcessOneChannel(CtlSourceT *source, const char* uid, json_object *channelJ, AlsaPcmChannels *channel) {
+ const char*channelUid;
+
+ int error = wrap_json_unpack(channelJ, "{ss,si !}", "uid", &channelUid, "port", &channel->port);
+ if (error) goto OnErrorExit;
+
+ channel->uid=strdup(channelUid);
+ return 0;
+
+OnErrorExit:
+ AFB_ApiError(source->api, "ProcessOneChannel: sndcard=%s channel: missing (uid||port) json=%s", uid, json_object_get_string(channelJ));
+ return -1;
+}
+
+STATIC int ProcessSndParams(CtlSourceT *source, const char* uid, json_object *paramsJ, AlsaPcmHwInfoT *params) {
+
+ int error = wrap_json_unpack(paramsJ, "{s?i,s?i !}", "rate", &params->rate, "channels", &params->channels);
+ if (error) goto OnErrorExit;
+
+ return 0;
+
+OnErrorExit:
+ AFB_ApiError(source->api, "ProcessSndParams: sndcard=%s params: missing (rate|channel) params=%s", uid, json_object_get_string(paramsJ));
+ return -1;
+}
+
+STATIC int ProcessOneSndCard(CtlSourceT *source, json_object *sndcardJ, AlsaPcmInfoT *snd) {
+ json_object *sinkJ, *paramsJ=NULL;
+ int error;
+
+ error = wrap_json_unpack(sndcardJ, "{ss,s?s,s?s,s?i,s?i,s?i,so,s?o !}", "uid",&snd->uid, "devpath",&snd->devpath, "cardid",&snd->cardid
+ , "cardidx",&snd->cardidx, "device",&snd->device, "subdev",&snd->subdev, "sink",&sinkJ, "params",&paramsJ);
+ if (error || !snd->uid || !sinkJ || (!snd->devpath && !snd->cardid && snd->cardidx)) {
+ AFB_ApiNotice(source->api, "ProcessOneSndCard missing 'uid|path|cardid|cardidx|channels|device|subdev|numid|params' devin=%s", json_object_get_string(sndcardJ));
+ goto OnErrorExit;
+ }
+
+ if (paramsJ) error= ProcessSndParams(source, snd->uid, paramsJ, &snd->params);
+ if (error) {
+ AFB_ApiError(source->api, "ProcessOneSndCard: sndcard=%s invalid params=%s", snd->uid, json_object_get_string(paramsJ));
+ goto OnErrorExit;
+ }
+
+
+ // check snd card is accessible
+ error = AlsaByPathDevid(source, snd);
+ if (error) {
+ AFB_ApiError(source->api, "ProcessOneSndCard: sndcard=%s not found config=%s", snd->uid, json_object_get_string(sndcardJ));
+ goto OnErrorExit;
+ }
+
+ // protect each sndcard with a dmix plugin to enable audio-stream mixing
+ AlsaPcmInfoT *dmixPcm= AlsaCreateDmix(source, snd->uid, snd);
+ if (!dmixPcm) {
+ AFB_ApiError(source->api, "ProcessOneSndCard: sndcard=%s fail to attach dmix plugin", snd->uid);
+ goto OnErrorExit;
+ } else {
+ snd_pcm_close(dmixPcm->handle);
+ snd->cardid=dmixPcm->cardid;
+ }
+
+ switch (json_object_get_type(sinkJ)) {
+ case json_type_object:
+ snd->ccount=1;
+ snd->channels = calloc(snd->ccount+1, sizeof (AlsaPcmChannels));
+ error = ProcessOneChannel(source, snd->uid, sndcardJ, &snd->channels[0]);
+ if (error) goto OnErrorExit;
+ break;
+ case json_type_array:
+ snd->ccount = (int)json_object_array_length(sinkJ);
+ snd->channels = calloc(snd->ccount+1, sizeof (AlsaPcmChannels));
+ for (int idx = 0; idx < snd->ccount; idx++) {
+ json_object *channelJ = json_object_array_get_idx(sinkJ, idx);
+ error = ProcessOneChannel(source, snd->uid, channelJ, &snd->channels[idx]);
+ if (error) goto OnErrorExit;
+ }
+ break;
+ default:
+ AFB_ApiError(source->api, "ProcessOneSndCard:%s invalid sink=%s", snd->uid, json_object_get_string(sinkJ));
+ goto OnErrorExit;
+ }
+
+ return 0;
+
+OnErrorExit:
+ return -1;
+}
+
+CTLP_LUA2C(snd_cards, source, argsJ, responseJ) {
+ AlsaPcmInfoT *sndcards;
+
+ int error;
+ size_t count;
+
+ switch (json_object_get_type(argsJ)) {
+ case json_type_object:
+ count= 1;
+ sndcards = calloc(count + 1, sizeof (AlsaPcmInfoT));
+ error = ProcessOneSndCard(source, argsJ, &sndcards[0]);
+ if (error) goto OnErrorExit;
+ break;
+ case json_type_array:
+ count = json_object_array_length(argsJ);
+ sndcards = calloc(count + 1, sizeof (AlsaPcmInfoT));
+ for (int idx = 0; idx < count; idx++) {
+ json_object *sndcardJ = json_object_array_get_idx(argsJ, idx);
+ error = ProcessOneSndCard(source, sndcardJ, &sndcards[idx]);
+ if (error) goto OnErrorExit;
+ }
+ break;
+ default:
+ AFB_ApiError(source->api, "L2C:sndcards: invalid argsJ= %s", json_object_get_string(argsJ));
+ goto OnErrorExit;
+ }
+
+ // register Sound card and multi when needed
+ Softmixer->sndcardCtl= sndcards;
+
+ if (count == 1) {
+
+ // only one sound card we multi would be useless
+ Softmixer->multiPcm = &sndcards[0];
+
+ } else {
+ AlsaPcmInfoT *pcmMulti;
+
+ // instantiate an alsa multi plugin
+ pcmMulti = AlsaCreateMulti(source, "PcmMulti");
+ if (!pcmMulti) goto OnErrorExit;
+
+ // Close Multi and save into globak handle for further use
+ snd_pcm_close(pcmMulti->handle);
+ Softmixer->multiPcm= pcmMulti;
+ }
+
+ return 0;
+
+OnErrorExit:
+ AFB_ApiNotice(source->api, "L2C:sndcards fail to process: %s", json_object_get_string(argsJ));
+ return -1;
+}
diff --git a/plugins/alsa/alsa-api-sndloops.c b/plugins/alsa/alsa-api-sndloops.c
new file mode 100644
index 0000000..5599ae0
--- /dev/null
+++ b/plugins/alsa/alsa-api-sndloops.c
@@ -0,0 +1,135 @@
+/*
+ * 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 <string.h>
+
+// Fulup need to be cleanup with new controller version
+extern Lua2cWrapperT Lua2cWrap;
+
+STATIC int ProcessOneSubdev(CtlSourceT *source, AlsaSndLoopT *loop, json_object *subdevJ, AlsaPcmInfoT *subdev) {
+
+ int error = wrap_json_unpack(subdevJ, "{si,si !}", "subdev", &subdev->subdev, "numid", &subdev->numid);
+ if (error) {
+ AFB_ApiError(source->api, "ProcessOneSubdev: loop=%s missing (uid|subdev|numid) json=%s", loop->uid, json_object_get_string(subdevJ));
+ goto OnErrorExit;
+ }
+
+ // create a fake uid and complete subdev info from loop handle
+ char subuid[30];
+ snprintf(subuid,sizeof(subuid),"loop:/%i/%i", subdev->subdev,subdev->numid);
+ subdev->uid = strdup(subuid);
+ subdev->device = loop->capture; // Fulup: with alsaloop softmixer only use capture device (playback is used by applications)
+ subdev->devpath = loop->devpath;
+ subdev->cardid = NULL; // force AlsaByPathDevId to rebuild a new one for each subdev
+ subdev->cardidx = loop->cardidx;
+
+ // check if card exist
+ error = AlsaByPathDevid(source, subdev);
+ if (error) {
+ AFB_ApiError(source->api, "ProcessOneSubdev: loop=%s fail to open subdev=%s", loop->uid, json_object_get_string(subdevJ));
+ goto OnErrorExit;
+ }
+
+ return 0;
+
+OnErrorExit:
+ return -1;
+}
+
+STATIC int ProcessOneLoop(CtlSourceT *source, json_object *loopJ, AlsaSndLoopT *loop) {
+ json_object *subdevsJ=NULL, *devicesJ=NULL;
+ int error;
+
+ error = wrap_json_unpack(loopJ, "{ss,s?s,s?s,s?i,s?o,so}", "uid",&loop->uid, "devpath",&loop->devpath, "cardid",&loop->cardid
+ , "cardidx",&loop->cardidx, "devices",&devicesJ, "subdevs",&subdevsJ);
+ if (error || !loop->uid || !subdevsJ || (!loop->devpath && !loop->cardid && loop->cardidx)) {
+ AFB_ApiNotice(source->api, "ProcessOneLoop missing 'uid|devpath|cardid|cardidx|devices|subdevs' loop=%s", json_object_get_string(loopJ));
+ goto OnErrorExit;
+ }
+
+ // make sure useful information will not be removed
+ loop->uid=strdup(loop->uid);
+ if (loop->cardid) loop->cardid=strdup(loop->cardid);
+ if (loop->devpath) loop->cardid=strdup(loop->devpath);
+
+ // Default devices is payback=0 capture=1
+ if (!devicesJ) {
+ loop->playback=0;
+ loop->capture=1;
+ } else {
+ error = wrap_json_unpack(devicesJ, "{si,si}", "capture",&loop->capture, "playback", &loop->playback);
+ if (error) {
+ AFB_ApiNotice(source->api, "ProcessOneLoop=%s missing 'capture|playback' devices=%s", loop->uid, json_object_get_string(devicesJ));
+ goto OnErrorExit;
+ }
+ }
+
+ switch (json_object_get_type(subdevsJ)) {
+ case json_type_object:
+ loop->scount = 1;
+ loop->subdevs = calloc(loop->scount+1, sizeof (AlsaPcmInfoT));
+ error = ProcessOneSubdev(source, loop, subdevsJ, &loop->subdevs[0]);
+ if (error) goto OnErrorExit;
+ break;
+ case json_type_array:
+ loop->scount = (int)json_object_array_length(subdevsJ);
+ loop->subdevs = calloc(loop->scount+1, sizeof (AlsaPcmInfoT));
+ for (int idx = 0; idx < loop->scount; idx++) {
+ json_object *subdevJ = json_object_array_get_idx(subdevsJ, idx);
+ error = ProcessOneSubdev(source, loop, subdevJ, &loop->subdevs[idx]);
+ if (error) goto OnErrorExit;
+ }
+ break;
+ default:
+ AFB_ApiError(source->api, "L2C:ProcessOneLoop=%s invalid subdevs= %s", loop->uid, json_object_get_string(subdevsJ));
+ goto OnErrorExit;
+ }
+
+ return 0;
+
+OnErrorExit:
+ return -1;
+}
+
+CTLP_LUA2C(snd_loops, source, argsJ, responseJ) {
+ int error;
+ AlsaSndLoopT *sndLoop = calloc (1, sizeof(AlsaSndLoopT));
+
+ if (json_object_get_type(argsJ) != json_type_object) {
+ AFB_ApiError(source->api, "L2C:sndloops: invalid object type= %s", json_object_get_string(argsJ));
+ goto OnErrorExit;
+ }
+
+ error = ProcessOneLoop(source, argsJ, sndLoop);
+ if (error) {
+ AFB_ApiError(source->api, "L2C:sndloops: invalid object= %s", json_object_get_string(argsJ));
+ goto OnErrorExit;
+ }
+
+ // register routed into global softmixer handle
+ Softmixer->loopCtl = sndLoop;
+
+
+ return 0;
+
+OnErrorExit:
+ return -1;
+} \ No newline at end of file
diff --git a/plugins/alsa/alsa-api-sndstreams.c b/plugins/alsa/alsa-api-sndstreams.c
new file mode 100644
index 0000000..9f5d90f
--- /dev/null
+++ b/plugins/alsa/alsa-api-sndstreams.c
@@ -0,0 +1,161 @@
+/*
+ * 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 <string.h>
+
+// Fulup need to be cleanup with new controller version
+extern Lua2cWrapperT Lua2cWrap;
+
+STATIC int ProcessOneStream(CtlSourceT *source, json_object *streamJ, AlsaSndStreamT *stream) {
+ int error;
+ const char*format=NULL;
+
+ error = wrap_json_unpack(streamJ, "{ss,ss,s?i,s?b,s?i,s?i,s?i !}", "uid", &stream->uid, "zone", &stream->zone, "volume", &stream->volume, "mute", stream->mute
+ , "rate", &stream->params.rate, "format", &format, "access", &stream->params.access);
+ if (error) {
+ AFB_ApiNotice(source->api, "ProcessOneStream missing 'uid|zone|volume|rate|mute' stream=%s", json_object_get_string(streamJ));
+ goto OnErrorExit;
+ }
+
+ if (!format) stream->params.format=SND_PCM_FORMAT_UNKNOWN;
+ else if (!strcasecmp (format, "S16_LE")) stream->params.format=SND_PCM_FORMAT_S16_LE;
+ else if (!strcasecmp (format, "S16_BE")) stream->params.format=SND_PCM_FORMAT_S16_BE;
+ else if (!strcasecmp (format, "U16_LE")) stream->params.format=SND_PCM_FORMAT_U16_LE;
+ else if (!strcasecmp (format, "U16_BE")) stream->params.format=SND_PCM_FORMAT_U16_BE;
+ else if (!strcasecmp (format, "S32_LE")) stream->params.format=SND_PCM_FORMAT_S32_LE;
+ else if (!strcasecmp (format, "S32_BE")) stream->params.format=SND_PCM_FORMAT_S32_BE;
+ else if (!strcasecmp (format, "U32_LE")) stream->params.format=SND_PCM_FORMAT_U32_LE;
+ else if (!strcasecmp (format, "U32_BE")) stream->params.format=SND_PCM_FORMAT_U32_BE;
+ else if (!strcasecmp (format, "S24_LE")) stream->params.format=SND_PCM_FORMAT_S24_LE;
+ else if (!strcasecmp (format, "S24_BE")) stream->params.format=SND_PCM_FORMAT_S24_BE;
+ else if (!strcasecmp (format, "U24_LE")) stream->params.format=SND_PCM_FORMAT_U24_LE;
+ else if (!strcasecmp (format, "U24_BE")) stream->params.format=SND_PCM_FORMAT_U24_BE;
+ else if (!strcasecmp (format, "S8")) stream->params.format=SND_PCM_FORMAT_S8;
+ else if (!strcasecmp (format, "U8")) stream->params.format=SND_PCM_FORMAT_U8;
+ else if (!strcasecmp (format, "FLOAT_LE")) stream->params.format=SND_PCM_FORMAT_FLOAT_LE;
+ else if (!strcasecmp (format, "FLOAT_BE")) stream->params.format=SND_PCM_FORMAT_FLOAT_LE;
+ else {
+ AFB_ApiNotice(source->api, "ProcessOneStream unsupported format 'uid|zone|volume|rate|mute' stream=%s", json_object_get_string(streamJ));
+ goto OnErrorExit;
+ }
+
+ if(!stream->params.rate) stream->params.rate=ALSA_DEFAULT_PCM_RATE;
+
+ // make sure remain valid even when json object is removed
+ stream->uid = strdup(stream->uid);
+ stream->zone = strdup(stream->zone);
+
+ return 0;
+
+OnErrorExit:
+ return -1;
+}
+
+CTLP_LUA2C(snd_streams, source, argsJ, responseJ) {
+ AlsaSndStreamT *sndStream;
+ int error;
+ size_t count;
+
+ // assert static/global softmixer handle get requited info
+ AlsaSndLoopT *ctlLoop = Softmixer->loopCtl;
+ if (!ctlLoop) {
+ AFB_ApiError(source->api, "L2C:sndstreams: No Loop found [should register snd_loop first]");
+ goto OnErrorExit;
+ }
+
+ switch (json_object_get_type(argsJ)) {
+ case json_type_object:
+ count = 1;
+ sndStream = calloc(count + 1, sizeof (AlsaSndStreamT));
+ error = ProcessOneStream(source, argsJ, &sndStream[0]);
+ if (error) {
+ AFB_ApiError(source->api, "L2C:sndstreams: invalid stream= %s", json_object_get_string(argsJ));
+ goto OnErrorExit;
+ }
+ break;
+
+ case json_type_array:
+ count = json_object_array_length(argsJ);
+ sndStream = calloc(count + 1, sizeof (AlsaSndStreamT));
+ for (int idx = 0; idx < count; idx++) {
+ json_object *sndStreamJ = json_object_array_get_idx(argsJ, idx);
+ error = ProcessOneStream(source, sndStreamJ, &sndStream[idx]);
+ if (error) {
+ AFB_ApiError(source->api, "sndstreams: invalid stream= %s", json_object_get_string(sndStreamJ));
+ goto OnErrorExit;
+ }
+ }
+ break;
+ default:
+ AFB_ApiError(source->api, "L2C:sndstreams: invalid argsJ= %s", json_object_get_string(argsJ));
+ goto OnErrorExit;
+ }
+
+ // instantiate stream as a softvol
+
+ for (int idx = 0; sndStream[idx].uid != NULL; idx++) {
+
+ // Search for a free loop capture device
+ AFB_ApiNotice(source->api, "L2C:sndstreams stream=%s Start", (char*)sndStream[idx].uid);
+ ctlLoop->scount--;
+ if (ctlLoop->scount < 0) {
+ AFB_ApiError(source->api, "L2C:sndstreams no more subdev avaliable in loopback=%s", ctlLoop->uid);
+ goto OnErrorExit;
+ }
+
+ // Retrieve subdev loop device and open corresponding pcm
+ AlsaPcmInfoT *devLoop = &ctlLoop->subdevs[ctlLoop->scount];
+ AlsaPcmInfoT *pcmLoop = AlsaByPathOpenPcm(source, devLoop, SND_PCM_STREAM_CAPTURE);
+ if (!pcmLoop) goto OnErrorExit;
+
+ AlsaPcmInfoT *streamPcm = AlsaCreateStream(source, &sndStream[idx], pcmLoop);
+ if (!streamPcm) {
+ AFB_ApiError(source->api, "L2C:sndstreams fail to create stream=%s", (char*) sndStream[idx].uid);
+ goto OnErrorExit;
+ }
+
+ // capture stream inherit channel from targeted zone
+ pcmLoop->ccount = streamPcm->ccount;
+ sndStream[idx].params.channels=streamPcm->ccount;
+
+ // start stream pcm copy
+ error = AlsaPcmCopy(source, pcmLoop, streamPcm, &sndStream[idx].params);
+ if (error) goto OnErrorExit;
+
+ // Registration to event should be done after pcm_start
+ if (pcmLoop->numid) {
+ error = AlsaCtlRegister(source, pcmLoop, pcmLoop->numid);
+ if (error) goto OnErrorExit;
+ }
+
+
+ // Debug Alsa Config
+ //AlsaDumpElemConfig (source, "\n\nAlsa_Config\n------------\n", "pcm");
+ AlsaDumpPcmInfo(source, "\n\nPcm_config\n-----------\n", streamPcm->handle);
+
+ AFB_ApiNotice(source->api, "L2C:sndstreams stream=%s OK\n", (char*) sndStream[idx].uid);
+ }
+
+ return 0;
+
+OnErrorExit:
+ return -1;
+} \ No newline at end of file
diff --git a/plugins/alsa/alsa-api-sndzones.c b/plugins/alsa/alsa-api-sndzones.c
new file mode 100644
index 0000000..11d77c2
--- /dev/null
+++ b/plugins/alsa/alsa-api-sndzones.c
@@ -0,0 +1,145 @@
+/*
+ * 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 <string.h>
+
+// Fulup need to be cleanup with new controller version
+extern Lua2cWrapperT Lua2cWrap;
+
+STATIC int ProcessOneChannel(CtlSourceT *source, const char* uid, json_object *channelJ, AlsaPcmChannels *channel) {
+ const char*channelUid;
+
+ int error = wrap_json_unpack(channelJ, "{ss,si,s?i !}", "target", &channelUid, "channel", &channel->port);
+ if (error) goto OnErrorExit;
+
+ channel->uid=strdup(channelUid);
+ return 0;
+
+OnErrorExit:
+ AFB_ApiError(source->api, "ProcessOneChannel: zone=%s channel: missing (target||channel) json=%s", uid, json_object_get_string(channelJ));
+ return -1;
+}
+
+STATIC int ProcessOneZone(CtlSourceT *source, json_object *zoneJ, AlsaSndZoneT *zone) {
+ json_object *mappingJ;
+ size_t count;
+ const char* streamType;
+ int error;
+
+ error = wrap_json_unpack(zoneJ, "{ss,s?s,so !}", "uid", &zone->uid, "type", &streamType, "mapping", &mappingJ);
+ if (error) {
+ AFB_ApiNotice(source->api, "ProcessOneone missing 'uid|type|mapping' zone=%s", json_object_get_string(zoneJ));
+ goto OnErrorExit;
+ }
+
+ if (!streamType) zone->type = SND_PCM_STREAM_PLAYBACK;
+ else {
+ if (!strcasecmp(streamType, "capture")) zone->type = SND_PCM_STREAM_CAPTURE;
+ else if (!strcasecmp(streamType, "playback")) zone->type = SND_PCM_STREAM_PLAYBACK;
+ else {
+ AFB_ApiError(source->api, "ProcessOneZone:%s invalid stream type !(playback||capture) json=%s", zone->uid, json_object_get_string(zoneJ));
+ goto OnErrorExit;
+ }
+ }
+
+ // make sure remain valid even when json object is removed
+ zone->uid= strdup(zone->uid);
+
+ switch (json_object_get_type(mappingJ)) {
+ case json_type_object:
+ count = 1;
+ zone->channels = calloc(count + 1, sizeof (AlsaPcmChannels));
+ error = ProcessOneChannel(source, zone->uid, mappingJ, &zone->channels[0]);
+ if (error) goto OnErrorExit;
+ break;
+ case json_type_array:
+ count = json_object_array_length(mappingJ);
+ zone->channels = calloc(count + 1, sizeof (AlsaPcmChannels));
+ for (int idx = 0; idx < count; idx++) {
+ json_object *channelJ = json_object_array_get_idx(mappingJ, idx);
+ error = ProcessOneChannel(source, zone->uid, channelJ, &zone->channels[idx]);
+ if (error) goto OnErrorExit;
+ }
+ break;
+ default:
+ AFB_ApiError(source->api, "ProcessOneZone:%s invalid mapping=%s", zone->uid, json_object_get_string(mappingJ));
+ goto OnErrorExit;
+ }
+
+ return 0;
+
+OnErrorExit:
+ return -1;
+}
+
+CTLP_LUA2C(snd_zones, source, argsJ, responseJ) {
+ AlsaSndZoneT *sndZone;
+ int error;
+ size_t count;
+
+ switch (json_object_get_type(argsJ)) {
+ case json_type_object:
+ count = 1;
+ sndZone = calloc(count + 1, sizeof (AlsaSndZoneT));
+ error = ProcessOneZone(source, argsJ, &sndZone[0]);
+ if (error) {
+ AFB_ApiError(source->api, "L2C:sndzones: invalid zone= %s", json_object_get_string(argsJ));
+ goto OnErrorExit;
+ }
+ break;
+
+ case json_type_array:
+ count = json_object_array_length(argsJ);
+ sndZone = calloc(count + 1, sizeof (AlsaSndZoneT));
+ for (int idx = 0; idx < count; idx++) {
+ json_object *sndZoneJ = json_object_array_get_idx(argsJ, idx);
+ error = ProcessOneZone(source, sndZoneJ, &sndZone[idx]);
+ if (error) {
+ AFB_ApiError(source->api, "L2C:sndzones: invalid zone= %s", json_object_get_string(sndZoneJ));
+ goto OnErrorExit;
+ }
+ }
+ break;
+ default:
+ AFB_ApiError(source->api, "L2C:sndzones: invalid argsJ= %s", json_object_get_string(argsJ));
+ goto OnErrorExit;
+ }
+
+ // register routed into global softmixer handle
+ Softmixer->zonePcms = calloc(count+1, sizeof (AlsaPcmInfoT*));
+
+ // instantiate one route PCM per zone with multi plugin as slave
+ for (int idx = 0; sndZone[idx].uid != NULL; idx++) {
+ Softmixer->zonePcms[idx] = AlsaCreateRoute(source, &sndZone[idx]);
+ if (!Softmixer->zonePcms[idx]) {
+ AFB_ApiNotice(source->api, "L2C:sndzones fail to create route zone=%s", sndZone[idx].uid);
+ goto OnErrorExit;
+ }
+ snd_pcm_close(Softmixer->zonePcms[idx]->handle);
+ }
+
+ // do not need this handle anymore
+ free (sndZone);
+ return 0;
+
+OnErrorExit:
+ return -1;
+} \ No newline at end of file
diff --git a/plugins/alsa/alsa-core-ctl.c b/plugins/alsa/alsa-core-ctl.c
index 5944fb8..38ce8cc 100644
--- a/plugins/alsa/alsa-core-ctl.c
+++ b/plugins/alsa/alsa-core-ctl.c
@@ -101,21 +101,21 @@ OnErrorExit:
}
-PUBLIC snd_ctl_t *AlsaCtlOpenCtl(CtlSourceT *source, const char *devid) {
+PUBLIC snd_ctl_t *AlsaCtlOpenCtl(CtlSourceT *source, const char *cardid) {
int error;
snd_ctl_t *ctlDev;
- if (devid) goto OnErrorExit;
+ if (cardid) goto OnErrorExit;
- if ((error = snd_ctl_open(&ctlDev, devid, SND_CTL_READONLY)) < 0) {
- devid = "Not Defined";
+ if ((error = snd_ctl_open(&ctlDev, cardid, SND_CTL_READONLY)) < 0) {
+ cardid = "Not Defined";
goto OnErrorExit;
}
return ctlDev;
OnErrorExit:
- AFB_ApiError(source->api, "AlsaCtlOpenCtl: fail to find sndcard by id= %s", devid);
+ AFB_ApiError(source->api, "AlsaCtlOpenCtl: fail to find sndcard by id= %s", cardid);
return NULL;
}
@@ -198,14 +198,14 @@ OnErrorExit:
}
// Clone of AlsaLib snd_card_load2 static function
-PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(CtlSourceT *source, const char *devid) {
+PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(CtlSourceT *source, const char *cardid) {
int error;
snd_ctl_t *ctlDev;
- if (devid) goto OnErrorExit;
+ if (cardid) goto OnErrorExit;
- if ((error = snd_ctl_open(&ctlDev, devid, SND_CTL_READONLY)) < 0) {
- devid = "Not Defined";
+ if ((error = snd_ctl_open(&ctlDev, cardid, SND_CTL_READONLY)) < 0) {
+ cardid = "Not Defined";
goto OnErrorExit;
}
@@ -216,7 +216,7 @@ PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(CtlSourceT *source, const char *devid
return cardInfo;
OnErrorExit:
- AFB_ApiError(source->api, "AlsaCtlGetInfo: fail to find sndcard by id= %s", devid);
+ AFB_ApiError(source->api, "AlsaCtlGetInfo: fail to find sndcard by id= %s", cardid);
return NULL;
}
@@ -245,6 +245,7 @@ STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, v
snd_ctl_event_t *eventId;
snd_ctl_elem_id_t *elemId;
long value;
+ int idx;
if ((revents & EPOLLHUP) != 0) {
AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB hanghup [card:%s disconnected]", subscribeHandle->info);
@@ -275,15 +276,20 @@ STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, v
error = CtlElemIdGetNumid(subscribeHandle->api, subscribeHandle->ctlDev, elemId, &numid);
if (error) goto OnErrorExit;
- for (int idx = 0; idx < AudioStreamHandle.count; idx++) {
+ for (idx = 0; idx < AudioStreamHandle.count; idx++) {
if (AudioStreamHandle.stream[idx].numid == numid) {
- const char *pcmName = AudioStreamHandle.stream[idx].pcm->devid;
+ const char *pcmName = AudioStreamHandle.stream[idx].pcm->cardid;
snd_pcm_pause(AudioStreamHandle.stream[idx].pcm->handle, !value);
AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d pcm=%s pause=%d numid=%d", subscribeHandle->info, subscribeHandle->tid, pcmName, !value, numid);
break;
}
}
-
+ if (idx == AudioStreamHandle.count) {
+ char cardName[32];
+ ALSA_CTL_UID(subscribeHandle->ctlDev,cardName);
+ AFB_ApiWarning(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d card=%s numid=%d (ignored)", subscribeHandle->info, subscribeHandle->tid, cardName, numid);
+ }
+
OnSuccessExit:
return 0;
@@ -345,7 +351,7 @@ PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t * ctlDev) {
subscribeHandle->ctlDev = ctlDev;
subscribeHandle->info = "ctlEvt";
- // subscribe for sndctl events attached to devid
+ // subscribe for sndctl events attached to cardid
if ((error = snd_ctl_subscribe_events(ctlDev, 1)) < 0) {
AFB_ApiError(source->api, "AlsaCtlSubscribe: fail sndcard=%s to subscribe events", ALSA_CTL_UID(ctlDev, string));
goto OnErrorExit;
@@ -389,7 +395,7 @@ PUBLIC int AlsaCtlRegister(CtlSourceT *source, AlsaPcmInfoT *pcm, int numid) {
// NumID are attached to sndcard retrieve ctldev from PCM
snd_ctl_t* ctlDev = AlsaCrlFromPcm(source, pcm->handle);
if (!ctlDev) {
- AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail attache sndcard", pcm->devid);
+ AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail attache sndcard", pcm->cardid);
goto OnErrorExit;
}
@@ -401,7 +407,7 @@ PUBLIC int AlsaCtlRegister(CtlSourceT *source, AlsaPcmInfoT *pcm, int numid) {
error = AlsaCtlGetNumidValueI(source, ctlDev, numid, &value);
if (error) goto OnErrorExit;
- AFB_ApiNotice(source->api, "AlsaCtlRegister [pcm=%s] numid=%d value=%ld", pcm->devid, numid, value);
+ AFB_ApiNotice(source->api, "AlsaCtlRegister [pcm=%s] numid=%d value=%ld", pcm->cardid, numid, value);
// store PCM in order to pause/resume depending on event
int count=AudioStreamHandle.count;
@@ -413,7 +419,7 @@ PUBLIC int AlsaCtlRegister(CtlSourceT *source, AlsaPcmInfoT *pcm, int numid) {
// toggle pause/resume (should be done after pcm_start)
if ((error = snd_pcm_pause(pcm->handle, !value)) < 0) {
- AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail to pause", pcm->devid);
+ AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail to pause", pcm->cardid);
goto OnErrorExit;
}
diff --git a/plugins/alsa/alsa-core-pcm.c b/plugins/alsa/alsa-core-pcm.c
index a08dfca..f143eb6 100644
--- a/plugins/alsa/alsa-core-pcm.c
+++ b/plugins/alsa/alsa-core-pcm.c
@@ -91,13 +91,13 @@ PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *op
if (!opts->access) opts->access = SND_PCM_ACCESS_RW_INTERLEAVED;
error = snd_pcm_hw_params_set_access(pcm->handle, pxmHwParams, opts->access);
if (error) {
- AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Interleave=%d mode error=%s", ALSA_PCM_UID(pcm->handle, string), opts->access, snd_strerror(error));
+ AFB_ApiError(source->api, "AlsaPcmConf:%s Fail PCM=%s Set_Interleave=%d mode error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), opts->access, snd_strerror(error));
goto OnErrorExit;
};
if (opts->format != SND_PCM_FORMAT_UNKNOWN) {
if ((error = snd_pcm_hw_params_set_format(pcm->handle, pxmHwParams, opts->format)) < 0) {
- AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Format=%d error=%s", ALSA_PCM_UID(pcm->handle, string), opts->format, snd_strerror(error));
+ AFB_ApiError(source->api, "AlsaPcmConf:%s Fail PCM=%s Set_Format=%d error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), opts->format, snd_strerror(error));
AlsaDumpFormats(source, pcm->handle);
goto OnErrorExit;
}
@@ -106,27 +106,27 @@ PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *op
if (opts->rate > 0) {
unsigned int pcmRate = opts->rate;
if ((error = snd_pcm_hw_params_set_rate_near(pcm->handle, pxmHwParams, &opts->rate, 0)) < 0) {
- AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Rate=%d error=%s", ALSA_PCM_UID(pcm->handle, string), opts->rate, snd_strerror(error));
+ AFB_ApiError(source->api, "AlsaPcmConf:%s Fail PCM=%s Set_Rate=%d error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), opts->rate, snd_strerror(error));
goto OnErrorExit;
}
// check we got requested rate
if (opts->rate != pcmRate) {
- AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Rate ask=%dHz get=%dHz", ALSA_PCM_UID(pcm->handle, string), pcmRate, opts->rate);
+ AFB_ApiError(source->api, "AlsaPcmConf:%s Fail PCM=%s Set_Rate ask=%dHz get=%dHz", pcm->uid, ALSA_PCM_UID(pcm->handle, string), pcmRate, opts->rate);
goto OnErrorExit;
}
}
if (opts->channels) {
if ((error = snd_pcm_hw_params_set_channels(pcm->handle, pxmHwParams, opts->channels)) < 0) {
- AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s Set_Channels=%d error=%s", ALSA_PCM_UID(pcm->handle, string), opts->channels, snd_strerror(error));
+ AFB_ApiError(source->api, "AlsaPcmConf:%s Fail PCM=%s Set_Channels=%d error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), opts->channels, snd_strerror(error));
goto OnErrorExit;
};
}
// store selected values
if ((error = snd_pcm_hw_params(pcm->handle, pxmHwParams)) < 0) {
- AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s apply hwparams error=%s", ALSA_PCM_UID(pcm->handle, string), snd_strerror(error));
+ AFB_ApiError(source->api, "AlsaPcmConf:%s Fail PCM=%s apply hwparams error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), snd_strerror(error));
goto OnErrorExit;
}
@@ -136,7 +136,7 @@ PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *op
snd_pcm_hw_params_get_rate(pxmHwParams, &opts->rate, 0);
opts->sampleSize = AlsaPeriodSize(opts->format);
if (opts->sampleSize == 0) {
- AFB_ApiError(source->api, "AlsaPcmConf: Fail PCM=%s unsupported format format=%d", ALSA_PCM_UID(pcm->handle, string), opts->format);
+ AFB_ApiError(source->api, "AlsaPcmConf:%s Fail PCM=%s unsupported format format=%d", pcm->uid, ALSA_PCM_UID(pcm->handle, string), opts->format);
goto OnErrorExit;
}
@@ -145,17 +145,17 @@ PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *op
snd_pcm_sw_params_current(pcm->handle, pxmSwParams);
if ((error = snd_pcm_sw_params_set_avail_min(pcm->handle, pxmSwParams, 16)) < 0) {
- AFB_ApiError(source->api, "AlsaPcmConf: Fail to PCM=%s set_buffersize error=%s", ALSA_PCM_UID(pcm->handle, string), snd_strerror(error));
+ AFB_ApiError(source->api, "AlsaPcmConf:%s Fail to PCM=%s set_buffersize error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), snd_strerror(error));
goto OnErrorExit;
};
// push software params into PCM
if ((error = snd_pcm_sw_params(pcm->handle, pxmSwParams)) < 0) {
- AFB_ApiError(source->api, "AlsaPcmConf: Fail to push software=%s params error=%s", ALSA_PCM_UID(pcm->handle, string), snd_strerror(error));
+ AFB_ApiError(source->api, "AlsaPcmConf:%s Fail to push software=%s params error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), snd_strerror(error));
goto OnErrorExit;
};
- AFB_ApiNotice(source->api, "AlsaPcmConf: PCM=%s channels=%d rate=%d format=%d access=%d done", ALSA_PCM_UID(pcm->handle,string), opts->channels, opts->rate, opts->format, opts->access);
+ AFB_ApiNotice(source->api, "AlsaPcmConf:%s PCM=%s channels=%d rate=%d format=%d access=%d done", pcm->uid, ALSA_PCM_UID(pcm->handle,string), opts->channels, opts->rate, opts->format, opts->access);
return 0;
OnErrorExit:
diff --git a/plugins/alsa/alsa-plug-dmix.c b/plugins/alsa/alsa-plug-dmix.c
index 3d67410..e28d009 100644
--- a/plugins/alsa/alsa-plug-dmix.c
+++ b/plugins/alsa/alsa-plug-dmix.c
@@ -28,14 +28,16 @@ ALSA_PLUG_PROTO(dmix);
PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave) {
snd_config_t *dmixConfig, *slaveConfig, *elemConfig, *pcmConfig;
- AlsaPcmInfoT *pcmPlug= malloc(sizeof(AlsaPcmInfoT));
- pcmPlug->devid= pcmName;
+ AlsaPcmInfoT *pcmPlug= calloc(1,sizeof(AlsaPcmInfoT));
+ pcmPlug->uid= strdup(pcmName);
+ pcmPlug->cardid=pcmPlug->uid;
+
int error=0;
// refresh global alsalib config and create PCM top config
snd_config_update();
error += snd_config_top(&dmixConfig);
- error += snd_config_set_id (dmixConfig, pcmPlug->devid);
+ error += snd_config_set_id (dmixConfig, pcmPlug->cardid);
error += snd_config_imake_string(&elemConfig, "type", "dmix");
error += snd_config_add(dmixConfig, elemConfig);
error += snd_config_imake_integer(&elemConfig, "ipc_key", uniqueIpcIndex++);
@@ -43,7 +45,11 @@ PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, Als
if (error) goto OnErrorExit;
error += snd_config_make_compound(&slaveConfig, "slave", 0);
- error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->devid);
+ error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->cardid);
+ if (pcmSlave->params.rate) {
+ error += snd_config_add(slaveConfig, elemConfig);
+ error += snd_config_imake_integer(&elemConfig, "rate", pcmSlave->params.rate);
+ }
error += snd_config_add(slaveConfig, elemConfig);
if (error) goto OnErrorExit;
@@ -51,26 +57,27 @@ PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, Als
error += snd_config_add(dmixConfig, slaveConfig);
if (error) goto OnErrorExit;
- error = _snd_pcm_dmix_open(&pcmPlug->handle, pcmPlug->devid, snd_config, dmixConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK);
+ error = _snd_pcm_dmix_open(&pcmPlug->handle, pcmPlug->cardid, snd_config, dmixConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK);
if (error) {
- AFB_ApiError(source->api, "AlsaCreateDmix: fail to create DMIX=%s SLAVE=%s", pcmPlug->devid, pcmSlave->devid);
+ AFB_ApiError(source->api, "AlsaCreateDmix: fail to create Dmix=%s Slave=%s Error=%s", pcmPlug->cardid, pcmSlave->cardid, snd_strerror(error));
goto OnErrorExit;
}
error += snd_config_search(snd_config, "pcm", &pcmConfig);
error += snd_config_add(pcmConfig, dmixConfig);
if (error) {
- AFB_ApiError(source->api, "AlsaCreateDmix: fail to add configDMIX=%s", pcmPlug->devid);
+ AFB_ApiError(source->api, "AlsaCreateDmix: fail to add configDMIX=%s", pcmPlug->cardid);
goto OnErrorExit;
}
// Debug config & pcm
- AlsaDumpCtlConfig (source, dmixConfig, 1);
- //AlsaDumpPcmInfo(source, pcmPlug->handle, pcmPlug->devid);
- AFB_ApiNotice(source->api, "AlsaCreateDmix: %s done", pcmPlug->devid);
+ AlsaDumpCtlConfig (source, "plug-dmix", dmixConfig, 1);
+ //AlsaDumpPcmInfo(source, pcmPlug->handle, pcmPlug->cardid);
+ AFB_ApiNotice(source->api, "AlsaCreateDmix: %s done\n", pcmPlug->cardid);
return pcmPlug;
OnErrorExit:
- AFB_ApiNotice(source->api, "AlsaCreateDmix: OnErrorExit");
+ AlsaDumpCtlConfig(source, "plug-dmix", dmixConfig, 1);
+ AFB_ApiNotice(source->api, "AlsaCreateDmix: OnErrorExit\n");
return NULL;
} \ No newline at end of file
diff --git a/plugins/alsa/alsa-plug-multi.c b/plugins/alsa/alsa-plug-multi.c
new file mode 100644
index 0000000..d6ef46e
--- /dev/null
+++ b/plugins/alsa/alsa-plug-multi.c
@@ -0,0 +1,105 @@
+/*
+ * 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"
+
+ALSA_PLUG_PROTO(multi);
+
+PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmUid) {
+
+ snd_config_t *multiConfig, *elemConfig, *slavesConfig, *slaveConfig, *bindingsConfig, *bindingConfig, *pcmConfig;
+ int error = 0, channelIdx=0;
+ AlsaPcmInfoT *pcmPlug = calloc(1, sizeof (AlsaPcmInfoT));
+ pcmPlug->uid = pcmUid;
+ pcmPlug->cardid = pcmUid;
+
+ AlsaPcmInfoT* pcmSlaves=Softmixer->sndcardCtl;
+ if (!Softmixer->sndcardCtl) {
+ AFB_ApiError(source->api, "AlsaCreateMulti: No Sound Card find [should register snd_cards first]");
+ goto OnErrorExit;
+ }
+
+ // refresh global alsalib config and create PCM top config
+ snd_config_update();
+ error += snd_config_top(&multiConfig);
+ error += snd_config_set_id (multiConfig, pcmPlug->cardid);
+ error += snd_config_imake_string(&elemConfig, "type", "multi");
+ error += snd_config_add(multiConfig, elemConfig);
+ if (error) goto OnErrorExit;
+
+ error += snd_config_make_compound(&slavesConfig, "slaves", 0);
+ error += snd_config_make_compound(&bindingsConfig, "bindings", 0);
+
+ // loop on sound card to include into multi
+ for (int idx = 0; pcmSlaves[idx].uid != NULL; idx++) {
+ AlsaPcmInfoT* sndcard=&pcmSlaves[idx];
+ AlsaPcmChannels *channels = sndcard->channels;
+
+ for (channelIdx=0; channels[channelIdx].uid != NULL; channelIdx++) {
+ char idxS[4]; // 999 channel should be more than enough
+ snprintf (idxS, sizeof(idxS), "%d", pcmPlug->ccount++);
+ // multi does not support to name channels
+ error += snd_config_make_compound(&bindingConfig,idxS, 0);
+ error += snd_config_imake_string(&elemConfig, "slave", sndcard->cardid);
+ error += snd_config_add(bindingConfig, elemConfig);
+ error += snd_config_imake_integer(&elemConfig,"channel", channelIdx);
+ error += snd_config_add(bindingConfig, elemConfig);
+ error += snd_config_add(bindingsConfig, bindingConfig);
+ }
+
+ error += snd_config_make_compound(&slaveConfig, sndcard->uid, 0);
+ error += snd_config_imake_string(&elemConfig, "pcm", sndcard->cardid);
+ error += snd_config_add(slaveConfig, elemConfig);
+ error += snd_config_imake_integer(&elemConfig, "channels", channelIdx);
+ error += snd_config_add(slaveConfig, elemConfig);
+ error += snd_config_add(slavesConfig, slaveConfig);
+ }
+
+ error += snd_config_add(multiConfig, slavesConfig);
+ error += snd_config_add(multiConfig, bindingsConfig);
+ if (error) goto OnErrorExit;
+
+ // update top config to access previous plugin PCM
+ snd_config_update();
+
+ error = _snd_pcm_multi_open(&pcmPlug->handle, pcmUid, snd_config, multiConfig, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+ if (error) {
+ AFB_ApiError(source->api, "AlsaCreateMulti: fail to create Plug=%s Error=%s", pcmPlug->cardid, snd_strerror(error));
+ goto OnErrorExit;
+ }
+
+ error += snd_config_search(snd_config, "pcm", &pcmConfig);
+ error += snd_config_add(pcmConfig, multiConfig);
+ if (error) {
+ AFB_ApiError(source->api, "AlsaCreateDmix: fail to add configDMIX=%s", pcmPlug->cardid);
+ goto OnErrorExit;
+ }
+
+ // Debug config & pcm
+ //AlsaDumpCtlConfig(source, "plug-multi", multiConfig, 1);
+ //AlsaDumpPcmInfo(source, pcmPlug->handle, "pcmPlug->handle");
+ AFB_ApiNotice(source->api, "AlsaCreateMulti: %s done\n", pcmPlug->cardid);
+ return pcmPlug;
+
+OnErrorExit:
+ AlsaDumpCtlConfig(source, "plug-multi", multiConfig, 1);
+ AFB_ApiNotice(source->api, "AlsaCreateMulti: OnErrorExit\n");
+ return NULL;
+} \ No newline at end of file
diff --git a/plugins/alsa/alsa-plug-route.c b/plugins/alsa/alsa-plug-route.c
new file mode 100644
index 0000000..dc75b56
--- /dev/null
+++ b/plugins/alsa/alsa-plug-route.c
@@ -0,0 +1,140 @@
+/*
+ * 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"
+
+ALSA_PLUG_PROTO(route);
+
+STATIC int CardChannelByUid(CtlSourceT *source, AlsaPcmInfoT *pcmBackend, const char *uid) {
+ int channelIdx = -1;
+
+ // search for channel within all sound card backend (channel port target is computed by order)
+ int targetIdx=0;
+ for (int cardIdx = 0; pcmBackend[cardIdx].uid != NULL; cardIdx++) {
+ AlsaPcmChannels *channels = pcmBackend[cardIdx].channels;
+ if (!channels) {
+ AFB_ApiError(source->api, "CardChannelByUid: No Backend card=%s [should declare channels]", pcmBackend[cardIdx].uid);
+ goto OnErrorExit;
+ }
+
+ for (int idx = 0; channels[idx].uid != NULL; idx++) {
+ if (!strcmp(channels[idx].uid, uid)) return targetIdx;
+ targetIdx++;
+ }
+ }
+
+ // this is OnErrorExit
+ return channelIdx;
+
+OnErrorExit:
+ return -1;
+}
+
+PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone) {
+
+ snd_config_t *routeConfig, *elemConfig, *slaveConfig, *tableConfig, *pcmConfig;
+ int error = 0;
+ AlsaPcmInfoT *pcmPlug = calloc(1, sizeof (AlsaPcmInfoT));
+ pcmPlug->uid = zone->uid;
+ pcmPlug->cardid = zone->uid;
+
+ AlsaPcmInfoT *pcmBackend = Softmixer->sndcardCtl;
+ AlsaPcmInfoT* pcmSlave=Softmixer->multiPcm;
+ if (!pcmBackend || !pcmSlave) {
+ AFB_ApiError(source->api, "AlsaCreateRoute:zone(%s)(zone) No Sound Card Ctl find [should register snd_cards first]", zone->uid);
+ goto OnErrorExit;
+ }
+
+ // refresh global alsalib config and create PCM top config
+ snd_config_update();
+ error += snd_config_top(&routeConfig);
+ error += snd_config_set_id(routeConfig, pcmPlug->cardid);
+ error += snd_config_imake_string(&elemConfig, "type", "route");
+ error += snd_config_add(routeConfig, elemConfig);
+ error += snd_config_make_compound(&slaveConfig, "slave", 0);
+ error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->cardid);
+ error += snd_config_add(slaveConfig, elemConfig);
+ error += snd_config_imake_integer(&elemConfig, "channels", pcmSlave->ccount);
+ error += snd_config_add(slaveConfig, elemConfig);
+ error += snd_config_add(routeConfig, slaveConfig);
+ error += snd_config_make_compound(&tableConfig, "ttable", 0);
+ error += snd_config_add(routeConfig, tableConfig);
+ if (error) goto OnErrorExit;
+
+ // tempry store to unable multiple channel to route to the same port
+ snd_config_t **cports;
+ cports = alloca(sizeof (snd_config_t*)*(pcmSlave->ccount + 1));
+ memset(cports, 0, (sizeof (snd_config_t*)*(pcmSlave->ccount + 1)));
+
+ // loop on sound card to include into multi
+ for (int idx = 0; zone->channels[idx].uid != NULL; idx++) {
+
+ int target = CardChannelByUid(source, pcmBackend, zone->channels[idx].uid);
+ if (target < 0) {
+ AFB_ApiError(source->api, "AlsaCreateRoute:zone(%s) fail to find channel=%s", zone->uid, zone->channels[idx].uid);
+ goto OnErrorExit;
+ }
+
+ int channel = zone->channels[idx].port;
+ double volume = 1.0; // currently only support 100%
+
+ // if channel entry does not exit into ttable create it now
+ if (!cports[channel]) {
+ pcmPlug->ccount++;
+ char channelS[4]; // 999 channel should be more than enough
+ snprintf(channelS, sizeof (channelS), "%d", channel);
+ error += snd_config_make_compound(&cports[channel], channelS, 0);
+ error += snd_config_add(tableConfig, cports[channel]);
+ }
+
+ // 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[channel], elemConfig);
+ if (error) goto OnErrorExit;
+ }
+
+ // update top config to access previous plugin PCM
+ snd_config_update();
+
+ error = _snd_pcm_route_open(&pcmPlug->handle, zone->uid, snd_config, routeConfig, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+ if (error) {
+ AFB_ApiError(source->api, "AlsaCreateRoute:zone(%s) fail to create Plug=%s error=%s", zone->uid, pcmPlug->cardid, snd_strerror(error));
+ goto OnErrorExit;
+ }
+
+ error += snd_config_search(snd_config, "pcm", &pcmConfig);
+ error += snd_config_add(pcmConfig, routeConfig);
+ if (error) {
+ AFB_ApiError(source->api, "AlsaCreateDmix:%s fail to add configDMIX=%s", zone->uid, pcmPlug->cardid);
+ goto OnErrorExit;
+ }
+
+ // Debug config & pcm
+ AlsaDumpCtlConfig(source, "plug-route", routeConfig, 1);
+ AFB_ApiNotice(source->api, "AlsaCreateRoute:zone(%s) done\n", zone->uid);
+ return pcmPlug;
+
+OnErrorExit:
+ AlsaDumpCtlConfig(source, "plug-route", routeConfig, 1);
+ AFB_ApiNotice(source->api, "AlsaCreateRoute:zone(%s) OnErrorExit\n", zone->uid);
+ return NULL;
+} \ No newline at end of file
diff --git a/plugins/alsa/alsa-plug-stream.c b/plugins/alsa/alsa-plug-stream.c
new file mode 100644
index 0000000..fccb309
--- /dev/null
+++ b/plugins/alsa/alsa-plug-stream.c
@@ -0,0 +1,121 @@
+/*
+ * 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"
+
+ALSA_PLUG_PROTO(softvol); // stream uses solftvol plugin
+
+STATIC AlsaPcmInfoT* SlaveZoneByUid(CtlSourceT *source, AlsaPcmInfoT **pcmZones, const char *uid) {
+ AlsaPcmInfoT *slaveZone= NULL;
+
+ // Loop on every registered zone pcm and extract (cardid) from (uid)
+ for (int idx = 0; pcmZones[idx] != NULL; idx++) {
+ if (!strcasecmp (pcmZones[idx]->uid, uid)) {
+ slaveZone= pcmZones[idx];
+ return slaveZone;
+ }
+ }
+
+ return NULL;
+}
+
+PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *ctlControl) {
+
+ snd_config_t *streamConfig, *elemConfig, *slaveConfig, *controlConfig,*pcmConfig;
+ int error = 0;
+ AlsaPcmInfoT *pcmPlug= malloc(sizeof(AlsaPcmInfoT));
+
+ // assert static/global softmixer handle get requited info
+ AlsaSndLoopT *ctlLoop = Softmixer->loopCtl;
+ if (!ctlLoop) {
+ AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) No Loop found [should register snd_loop first]",stream->uid);
+ goto OnErrorExit;
+ }
+
+ // assert static/global softmixer handle get requited info
+ AlsaPcmInfoT **pcmZones = Softmixer->zonePcms;
+ if (!pcmZones) {
+ AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) No Zone found [should register snd_zones first]", stream->uid);
+ goto OnErrorExit;
+ }
+
+ // search for target zone uid
+ pcmPlug->uid= stream->uid;
+ pcmPlug->cardid= stream->uid;
+ AlsaPcmInfoT *pcmSlave= SlaveZoneByUid (source, pcmZones, stream->zone);
+ if (!pcmSlave || !pcmSlave->uid) {
+ AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) fail to find Zone=%s", pcmPlug->uid, stream->zone);
+ goto OnErrorExit;
+ }
+
+ // stream inherit from zone channel count
+ pcmPlug->ccount= pcmSlave->ccount;
+
+ // refresh global alsalib config and create PCM top config
+ snd_config_update();
+ error += snd_config_top(&streamConfig);
+ error += snd_config_set_id (streamConfig, pcmPlug->cardid);
+ error += snd_config_imake_string(&elemConfig, "type", "softvol");
+ error += snd_config_add(streamConfig, elemConfig);
+ if (error) goto OnErrorExit;
+
+ // add slave leaf
+ error += snd_config_make_compound(&slaveConfig, "slave", 0);
+ error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->cardid);
+ error += snd_config_add(slaveConfig, elemConfig);
+ error += snd_config_add(streamConfig, slaveConfig);
+ if (error) goto OnErrorExit;
+
+ // add control leaf
+ error += snd_config_make_compound(&controlConfig, "control", 0);
+ error += snd_config_imake_string(&elemConfig, "name", stream->uid);
+ error += snd_config_add(controlConfig, elemConfig);
+ error += snd_config_imake_integer(&elemConfig, "card", ctlControl->cardidx);
+ error += snd_config_add(controlConfig, elemConfig);
+ error += snd_config_add(streamConfig, controlConfig);
+ if (error) goto OnErrorExit;
+
+ // update top config to access previous plugin PCM
+ snd_config_update();
+
+ error = _snd_pcm_softvol_open(&pcmPlug->handle, stream->uid, snd_config, streamConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK);
+ if (error) {
+ AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) fail to create Plug=%s Slave=%s error=%s", stream->uid, pcmPlug->cardid, pcmSlave->cardid, snd_strerror(error));
+ goto OnErrorExit;
+ }
+
+ error += snd_config_search(snd_config, "pcm", &pcmConfig);
+ error += snd_config_add(pcmConfig, streamConfig);
+ if (error) {
+ AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) fail to add config", stream->uid);
+ goto OnErrorExit;
+ }
+
+ // Debug config & pcm
+ AlsaDumpCtlConfig (source, "plug-stream", streamConfig, 1);
+ //AlsaDumpPcmInfo(source, pcmPlug->handle, "pcmPlug->handle");
+ AFB_ApiNotice(source->api, "AlsaCreateStream:%s(stream) done\n", stream->uid);
+ return pcmPlug;
+
+OnErrorExit:
+ AlsaDumpCtlConfig(source, "plug-stream", streamConfig, 1);
+ AFB_ApiNotice(source->api, "AlsaCreateStream:%s(stream) OnErrorExit\n", stream->uid);
+ return NULL;
+} \ No newline at end of file
diff --git a/plugins/alsa/alsa-plug-vol.c b/plugins/alsa/alsa-plug-vol.c
deleted file mode 100644
index 6de4ddf..0000000
--- a/plugins/alsa/alsa-plug-vol.c
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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"
-
-ALSA_PLUG_PROTO(softvol);
-
-PUBLIC AlsaPcmInfoT* AlsaCreateVol(CtlSourceT *source, const char *pcmName, AlsaPcmInfoT* ctlTarget, AlsaPcmInfoT* pcmSlave) {
-
- snd_config_t *volConfig, *elemConfig, *slaveConfig, *controlConfig,*pcmConfig;
- int error = 0;
- AlsaPcmInfoT *pcmPlug= malloc(sizeof(AlsaPcmInfoT));
- pcmPlug->devid= pcmName;
-
- // refresh global alsalib config and create PCM top config
- snd_config_update();
- error += snd_config_top(&volConfig);
-
- // add slave leaf
- error += snd_config_make_compound(&slaveConfig, "slave", 0);
- error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->devid);
- error += snd_config_add(slaveConfig, elemConfig);
- error += snd_config_add(volConfig, slaveConfig);
- if (error) goto OnErrorExit;
-
- // add control leaf
- error += snd_config_make_compound(&controlConfig, "control", 0);
- error += snd_config_imake_string(&elemConfig, "name", pcmName);
- error += snd_config_add(controlConfig, elemConfig);
- error += snd_config_imake_integer(&elemConfig, "card", ctlTarget->cardid);
- error += snd_config_add(controlConfig, elemConfig);
- error += snd_config_add(volConfig, controlConfig);
- if (error) goto OnErrorExit;
-
- // update top config to access previous plugin PCM
- snd_config_update();
-
- error = _snd_pcm_softvol_open(&pcmPlug->handle, pcmName, snd_config, volConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK);
- if (error) {
- AFB_ApiError(source->api, "AlsaCreateVol: fail to create Plug=%s Slave=%s", pcmPlug->devid, pcmSlave->devid);
- goto OnErrorExit;
- }
-
- error += snd_config_search(snd_config, "pcm", &pcmConfig);
- error += snd_config_add(pcmConfig, volConfig);
- if (!error) {
- AFB_ApiError(source->api, "AlsaCreateDmix: fail to add configDMIX=%s", pcmPlug->devid);
- goto OnErrorExit;
- }
-
- // Debug config & pcm
- AlsaDumpCtlConfig (source, volConfig, 1);
- //AlsaDumpPcmInfo(source, pcmPlug->handle, "pcmPlug->handle");
- AFB_ApiNotice(source->api, "AlsaCreateVol: %s done", pcmPlug->devid);
- return pcmPlug;
-
-OnErrorExit:
- AFB_ApiNotice(source->api, "AlsaCreateVol: OnErrorExit");
- return NULL;
-} \ No newline at end of file
diff --git a/plugins/alsa/alsa-softmixer.c b/plugins/alsa/alsa-softmixer.c
index 9586fa3..bd9ee5e 100644
--- a/plugins/alsa/alsa-softmixer.c
+++ b/plugins/alsa/alsa-softmixer.c
@@ -21,12 +21,16 @@
#include "alsa-softmixer.h"
// Force Lua2cWrapper inclusion within already existing plugin
+
CTLP_LUA_REGISTER("alsa-mixer")
+SoftMixerHandleT *Softmixer;
+
// Call at initialisation time
CTLP_ONLOAD(plugin, callbacks) {
- AFB_ApiDebug (plugin->api, "SoftMixer plugin: uid='%s' 'info='%s'", plugin->uid, plugin->info);
- return NULL;
+ AFB_ApiDebug(plugin->api, "SoftMixer plugin: uid='%s' 'info='%s'", plugin->uid, plugin->info);
+ Softmixer = calloc(1, sizeof(SoftMixerHandleT));
+ return NULL;
}
CTLP_LUA2C(AlsaDmix, source, argsJ, responseJ) {
@@ -40,75 +44,74 @@ CTLP_LUA2C(AlsaDmix, source, argsJ, responseJ) {
}
+
CTLP_LUA2C(AlsaRouter, source, argsJ, responseJ) {
- json_object *sndInJ, *sndOutJ, *paramsJ=NULL;
+ json_object *sndInJ, *sndOutJ, *paramsJ = NULL;
AlsaPcmInfoT *sndIn, *sndOut;
int error;
-
+
// make sndIn/Out a pointer to get cleaner code
- sndIn=calloc(1, sizeof(AlsaPcmInfoT));
- sndOut=calloc(1, sizeof(AlsaPcmInfoT));
-
+ sndIn = calloc(1, sizeof (AlsaPcmInfoT));
+ sndOut = calloc(1, sizeof (AlsaPcmInfoT));
+
// set pcm options to defaults
AlsaPcmHwInfoT *pcmOpts;
- pcmOpts=calloc(1, sizeof(AlsaPcmHwInfoT));
- pcmOpts->format=SND_PCM_FORMAT_UNKNOWN;
- pcmOpts->access=SND_PCM_ACCESS_RW_INTERLEAVED;
-
- error= wrap_json_unpack(argsJ, "{s:o,s:o,s?o}", "devin", &sndInJ, "devout", &sndOutJ, "params", &paramsJ);
+ pcmOpts = calloc(1, sizeof (AlsaPcmHwInfoT));
+ pcmOpts->format = SND_PCM_FORMAT_UNKNOWN;
+ pcmOpts->access = SND_PCM_ACCESS_RW_INTERLEAVED;
+
+ error = wrap_json_unpack(argsJ, "{s:o,s:o,s?o}", "devin", &sndInJ, "devout", &sndOutJ, "params", &paramsJ);
if (error) {
AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter ARGS missing devIn|devOut args=%s", json_object_get_string(argsJ));
goto OnErrorExit;
}
- error= wrap_json_unpack(sndInJ, "{s?s,s?s,s?i,s?i,s?i}", "path",&sndIn->devpath, "id",&sndIn->devid, "numid",&sndIn->numid, "dev",&sndIn->device, "sub",&sndIn->subdev);
- if (error || (!sndIn->devpath && !sndIn->devid)) {
+ error = wrap_json_unpack(sndInJ, "{s?s,s?s,s?i,s?i,s?i}", "path", &sndIn->devpath, "id", &sndIn->cardid, "numid", &sndIn->numid, "dev", &sndIn->device, "sub", &sndIn->subdev);
+ if (error || (!sndIn->devpath && !sndIn->cardid)) {
AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter DEV-IN missing 'path|id|dev|sub|numid' devin=%s", json_object_get_string(sndInJ));
goto OnErrorExit;
}
-
- error= wrap_json_unpack(sndOutJ, "{s?s,s?s,s?i,s?i, s?i}", "path",&sndOut->devpath, "id",&sndOut->devid, "numid",&sndOut->numid,"dev",&sndOut->device, "sub",&sndOut->subdev);
- if (error || (!sndOut->devpath && !sndOut->devid)) {
+
+ error = wrap_json_unpack(sndOutJ, "{s?s,s?s,s?i,s?i, s?i}", "path", &sndOut->devpath, "id", &sndOut->cardid, "numid", &sndOut->numid, "dev", &sndOut->device, "sub", &sndOut->subdev);
+ if (error || (!sndOut->devpath && !sndOut->cardid)) {
AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter DEV-OUT missing 'path|id|dev|sub' devout=%s", json_object_get_string(sndOutJ));
goto OnErrorExit;
}
-
- if (paramsJ) if ((error= wrap_json_unpack(paramsJ, "{s?i, s?i, s?i, s?i}", "format", &pcmOpts->format, "access", &pcmOpts->access, "rate", &pcmOpts->rate, "channels",&pcmOpts->channels)) != 0) {
- AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter PARAMS missing 'format|access|rate|channels' params=%s", json_object_get_string(paramsJ));
- goto OnErrorExit;
- }
-
+
+ if (paramsJ) if ((error = wrap_json_unpack(paramsJ, "{s?i, s?i, s?i, s?i}", "format", &pcmOpts->format, "access", &pcmOpts->access, "rate", &pcmOpts->rate, "channels", &pcmOpts->channels)) != 0) {
+ AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter PARAMS missing 'format|access|rate|channels' params=%s", json_object_get_string(paramsJ));
+ goto OnErrorExit;
+ }
+
AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter devin=%s devout=%s rate=%d channel=%d", sndIn->devpath, sndOut->devpath, pcmOpts->rate, pcmOpts->channels);
-
- // Check sndOut Exist and build a valid devid config
- error= AlsaByPathDevid (source, sndOut);
+
+ // Check sndOut Exist and build a valid cardid config
+ error = AlsaByPathDevid(source, sndOut);
if (error) goto OnErrorExit;
-
- //AlsaPcmInfoT *pcmOut = AlsaByPathOpenPcm(source, sndOut, SND_PCM_STREAM_PLAYBACK);
-
+
// open capture PCM
AlsaPcmInfoT *pcmIn = AlsaByPathOpenPcm(source, sndIn, SND_PCM_STREAM_CAPTURE);
if (!pcmIn) goto OnErrorExit;
-
- AlsaPcmInfoT *pcmDmix= AlsaCreateDmix(source, "DmixPlugPcm", sndOut);
- if(!pcmDmix) goto OnErrorExit;
-
- AlsaPcmInfoT *pcmVol= AlsaCreateVol(source, "SoftVol", sndIn, pcmDmix);
- if(!pcmVol) goto OnErrorExit;
-
- error = AlsaPcmCopy(source, pcmIn, pcmVol, pcmOpts);
- if(error) goto OnErrorExit;
-
+
+ AlsaPcmInfoT *pcmDmix = AlsaCreateDmix(source, "DmixPlugPcm", sndOut);
+ if (!pcmDmix) goto OnErrorExit;
+
+ //AlsaPcmInfoT *pcmVol = AlsaCreateVol(source, "SoftVol", sndIn, pcmDmix);
+ //if (!pcmVol) goto OnErrorExit;
+
+ //error = AlsaPcmCopy(source, pcmIn, pcmVol, pcmOpts);
+ //if (error) goto OnErrorExit;
+
// Registration to event should be done after pcm_start
if (sndIn->numid) {
- error= AlsaCtlRegister(source, pcmIn, sndIn->numid);
- if(error) goto OnErrorExit;
- }
-
+ error = AlsaCtlRegister(source, pcmIn, sndIn->numid);
+ if (error) goto OnErrorExit;
+ }
+
return 0;
-
+
OnErrorExit:
AFB_ApiNotice(source->api, "--lua2c-- ERROR AlsaRouter sndIn=%s sndOut=%s rate=%d channel=%d", sndIn->devpath, sndOut->devpath, pcmOpts->rate, pcmOpts->channels);
- return -1;
+ return -1;
}
diff --git a/plugins/alsa/alsa-softmixer.h b/plugins/alsa/alsa-softmixer.h
index 17e7cf2..db499c3 100644
--- a/plugins/alsa/alsa-softmixer.h
+++ b/plugins/alsa/alsa-softmixer.h
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
-*/
+ */
#define _GNU_SOURCE // needed for vasprintf
@@ -25,6 +25,7 @@
#include <systemd/sd-event.h>
#include <json-c/json_object.h>
#include <stdio.h>
+#include <stdlib.h>
#include "ctl-plugin.h"
#include "wrap-json.h"
@@ -33,59 +34,114 @@
#define MAINLOOP_WATCHDOG 30000
#define MAX_AUDIO_STREAMS 8
+#define ALSA_DEFAULT_PCM_RATE 48000
+#define ALSA_CARDID_MAX_LEN 32
+
#define ALSA_PLUG_PROTO(plugin) \
int _snd_pcm_ ## plugin ## _open(snd_pcm_t **pcmp, const char *name, snd_config_t *root, snd_config_t *conf, snd_pcm_stream_t stream, int mode)
+
+
// alsa-utils-bypath.c
typedef struct {
+ const char*uid;
+ int port;
+} AlsaPcmChannels;
+
+typedef struct {
+ unsigned int rate;
+ unsigned int channels;
+ snd_pcm_format_t format;
+ snd_pcm_access_t access;
+ size_t sampleSize;
+} AlsaPcmHwInfoT;
+
+typedef struct {
+ const char *uid;
const char *devpath;
- const char *devid;
+ const char *cardid;
+ int cardidx;
int device;
int subdev;
int numid;
- int cardid;
+ int ccount;
snd_pcm_t *handle;
+ AlsaPcmChannels *channels;
+ AlsaPcmHwInfoT params;
} AlsaPcmInfoT;
typedef struct {
- unsigned int rate;
- unsigned int channels;
- snd_pcm_format_t format;
- snd_pcm_access_t access;
- size_t sampleSize;
-} AlsaPcmHwInfoT;
+ const char *uid;
+ snd_pcm_stream_t type;
+ AlsaPcmChannels *channels;
+ AlsaPcmInfoT *pcm;
+} AlsaSndZoneT;
+
+typedef struct {
+ const char *uid;
+ const char *devpath;
+ const char *cardid;
+ int cardidx;
+ int playback;
+ int capture;
+ int scount;
+ AlsaPcmInfoT *subdevs;
+} AlsaSndLoopT;
+
+
-PUBLIC snd_ctl_card_info_t* AlsaByPathInfo (CtlSourceT *source, const char *control);
+typedef struct {
+ const char *uid;
+ const char *zone;
+ int volume;
+ int mute;
+ AlsaPcmInfoT *pcm;
+ AlsaPcmHwInfoT params;
+} AlsaSndStreamT;
+
+typedef struct {
+ AlsaSndLoopT *loopCtl;
+ AlsaPcmInfoT *sndcardCtl;
+ AlsaPcmInfoT *multiPcm;
+ AlsaPcmInfoT **zonePcms;
+} SoftMixerHandleT;
+
+extern SoftMixerHandleT *Softmixer;
+
+PUBLIC snd_ctl_card_info_t* AlsaByPathInfo(CtlSourceT *source, const char *control);
PUBLIC AlsaPcmInfoT* AlsaByPathOpenPcm(CtlSourceT *source, AlsaPcmInfoT *dev, snd_pcm_stream_t direction);
-PUBLIC snd_ctl_t *AlsaByPathOpenCtl (CtlSourceT *source, AlsaPcmInfoT *dev);
-PUBLIC int AlsaByPathDevid (CtlSourceT *source, AlsaPcmInfoT *dev);
+PUBLIC snd_ctl_t *AlsaByPathOpenCtl(CtlSourceT *source, AlsaPcmInfoT *dev);
+PUBLIC int AlsaByPathDevid(CtlSourceT *source, AlsaPcmInfoT *dev);
// alsa-utils-dump.c
PUBLIC void AlsaDumpFormats(CtlSourceT *source, snd_pcm_t *pcmHandle);
PUBLIC char *AlsaDumpPcmUid(snd_pcm_t *pcmHandle, char *buffer, size_t len);
PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle);
-PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, snd_pcm_t *pcm, const char* info);
+PUBLIC void AlsaDumpElemConfig(CtlSourceT *source, const char* info, const char* elem);
+PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, const char* info, snd_pcm_t *pcm);
PUBLIC void AlsaDumpPcmParams(CtlSourceT *source, snd_pcm_hw_params_t *pcmHwParams);
-PUBLIC void AlsaDumpCtlConfig(CtlSourceT *source, snd_config_t *config, int indent);
+PUBLIC void AlsaDumpCtlConfig(CtlSourceT *source, const char* info, snd_config_t *config, int indent);
#define ALSA_PCM_UID(pcmHandle, buffer) AlsaDumpPcmUid(pcmHandle, buffer, sizeof(buffer))
PUBLIC char *AlsaDumpCtlUid(snd_ctl_t *ctlHandle, char *buffer, size_t len);
#define ALSA_CTL_UID(ctlHandle, buffer) AlsaDumpCtlUid(ctlHandle, buffer, sizeof(buffer))
// alsa-core-ctl.c
-PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo (CtlSourceT *source, const char *devid);
-PUBLIC snd_ctl_t *AlsaCtlOpenCtl (CtlSourceT *source, const char *devid);
+PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(CtlSourceT *source, const char *cardid);
+PUBLIC snd_ctl_t *AlsaCtlOpenCtl(CtlSourceT *source, const char *cardid);
PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t *ctlDev);
PUBLIC int AlsaCtlRegister(CtlSourceT *source, AlsaPcmInfoT *pcm, int numid);
PUBLIC int AlsaCtlGetNumidValueI(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long* value);
-
-
+// alsa-core-pcm.c
PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *opts);
PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pcmOut, AlsaPcmHwInfoT *opts);
-// _snd_pcm_PLUGIN_open_ see macro ALSA_PLUG_PROTO(plugin)
+// alsa-plug-*.c _snd_pcm_PLUGIN_open_ see macro ALSA_PLUG_PROTO(plugin)
PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave);
-PUBLIC AlsaPcmInfoT* AlsaCreateVol(CtlSourceT *source, const char *pcmName, AlsaPcmInfoT* ctlTarget, AlsaPcmInfoT* pcmSlave);
+PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmName);
+PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *pcmControl);
+PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone);
+
#endif \ No newline at end of file
diff --git a/plugins/alsa/alsa-utils-bypath.c b/plugins/alsa/alsa-utils-bypath.c
index 8fba1f0..7ceb66f 100644
--- a/plugins/alsa/alsa-utils-bypath.c
+++ b/plugins/alsa/alsa-utils-bypath.c
@@ -62,25 +62,39 @@ PUBLIC int AlsaByPathDevid(CtlSourceT *source, AlsaPcmInfoT *dev) {
// get card info from /dev/snd/xxx if not use hw:x,x,x
snd_ctl_card_info_t *cardInfo = NULL;
- if (dev->devpath) cardInfo = AlsaByPathInfo(source, dev->devpath);
- else if (dev->devid) cardInfo = AlsaCtlGetInfo(source, dev->devid);
+ if (dev->devpath) {
+ cardInfo = AlsaByPathInfo(source, dev->devpath);
+ dev->cardid=NULL;
+ }
+ else if (dev->cardid) {
+ dev->cardid= strdup(dev->cardid);
+ cardInfo = AlsaCtlGetInfo(source, dev->cardid);
+ }
+ else {
+ dev->cardid=malloc(ALSA_CARDID_MAX_LEN);
+ snprintf((char*)dev->cardid, ALSA_CARDID_MAX_LEN, "hw:%i", dev->cardidx);
+ cardInfo = AlsaCtlGetInfo(source, dev->cardid);
+ cardInfo = AlsaCtlGetInfo(source, dev->cardid);
+ }
if (!cardInfo) {
- AFB_ApiWarning(source->api, "AlsaByPathOpenPcm: fail to find sndcard by path=%s id=%s", dev->devpath, dev->devid);
+ AFB_ApiWarning(source->api, "AlsaByPathOpenPcm: fail to find sndcard by path=%s id=%s", dev->devpath, dev->cardid);
goto OnErrorExit;
}
// extract useful info from cardInfo handle
- dev->cardid = snd_ctl_card_info_get_card(cardInfo);
-
- // if not provided build a valid PCM devid
- if (!dev->devid) {
- #define DEVID_MAX_LEN 32
- dev->devid=malloc(DEVID_MAX_LEN);
- if (dev->subdev) snprintf((char*)dev->devid, DEVID_MAX_LEN, "hw:%i,%i,%i", dev->cardid, dev->device, dev->subdev);
- else if (dev->device) snprintf((char*)dev->devid, DEVID_MAX_LEN, "hw:%i,%i", dev->cardid, dev->device);
- else snprintf((char*)dev->devid, DEVID_MAX_LEN, "hw:%i", dev->cardid);
+ dev->cardidx = snd_ctl_card_info_get_card(cardInfo);
+
+ // if not provided build a valid PCM cardid
+ if (!dev->cardid) {
+ dev->cardid=malloc(ALSA_CARDID_MAX_LEN);
+ if (dev->subdev) snprintf((char*)dev->cardid, ALSA_CARDID_MAX_LEN, "hw:%i,%i,%i", dev->cardidx, dev->device, dev->subdev);
+ else if (dev->device) snprintf((char*)dev->cardid, ALSA_CARDID_MAX_LEN, "hw:%i,%i", dev->cardidx, dev->device);
+ else snprintf((char*)dev->cardid, ALSA_CARDID_MAX_LEN, "hw:%i", dev->cardidx);
}
+
+ // make sure UID will cannot be removed
+ dev->uid= strdup(dev->uid);
return 0;
OnErrorExit:
@@ -89,18 +103,23 @@ OnErrorExit:
PUBLIC AlsaPcmInfoT* AlsaByPathOpenPcm(CtlSourceT *source, AlsaPcmInfoT *dev, snd_pcm_stream_t direction) {
int error;
-
- error = AlsaByPathDevid(source, dev);
+
+ // duplicate dev structure to allow caller to free dev
+ AlsaPcmInfoT* pcm=malloc(sizeof(AlsaPcmInfoT));
+ memcpy (pcm, dev, sizeof(AlsaPcmInfoT));
+
+
+ error = AlsaByPathDevid(source, pcm);
if (error) goto OnErrorExit;
- error = snd_pcm_open(&dev->handle, dev->devid, direction, SND_PCM_NONBLOCK);
+ error = snd_pcm_open(&pcm->handle, pcm->cardid, direction, SND_PCM_NONBLOCK);
if (error) {
- AFB_ApiError(source->api, "AlsaByPathOpenPcm: fail openpcm (devid=%s idxdev=%i subdev=%d): %s"
- , dev->devid, dev->device, dev->subdev, snd_strerror(error));
+ AFB_ApiError(source->api, "AlsaByPathOpenPcm: fail openpcm (cardid=%s idxdev=%i subdev=%d): %s"
+ , pcm->cardid, pcm->device, pcm->subdev, snd_strerror(error));
goto OnErrorExit;
}
- return (dev);
+ return (pcm);
OnErrorExit:
return NULL;
@@ -108,16 +127,16 @@ OnErrorExit:
PUBLIC snd_ctl_t *AlsaByPathOpenCtl(CtlSourceT *source, AlsaPcmInfoT *dev) {
int err;
- char devid[32];
+ char cardid[32];
snd_ctl_t *handle;
// get card info from /dev/snd/xxx if not use hw:x,x,x
snd_ctl_card_info_t *cardInfo = NULL;
if (dev->devpath) cardInfo = AlsaByPathInfo(source, dev->devpath);
- else if (dev->devid) cardInfo = AlsaCtlGetInfo(source, dev->devid);
+ else if (dev->cardid) cardInfo = AlsaCtlGetInfo(source, dev->cardid);
if (!cardInfo) {
- AFB_ApiError(source->api, "AlsaByPathOpenCtl: fail to find sndcard by path=%s id=%s", dev->devpath, dev->devid);
+ AFB_ApiError(source->api, "AlsaByPathOpenCtl: fail to find sndcard by path=%s id=%s", dev->devpath, dev->cardid);
goto OnErrorExit;
}
@@ -127,8 +146,8 @@ PUBLIC snd_ctl_t *AlsaByPathOpenCtl(CtlSourceT *source, AlsaPcmInfoT *dev) {
const char *cardName = snd_ctl_card_info_get_name(cardInfo);
// build a valid name and open sndcard
- snprintf(devid, sizeof (devid), "hw:%i", cardIndex);
- if ((err = snd_ctl_open(&handle, devid, 0)) < 0) {
+ snprintf(cardid, sizeof (cardid), "hw:%i", cardIndex);
+ if ((err = snd_ctl_open(&handle, cardid, 0)) < 0) {
AFB_ApiError(source->api, "control open (hw:%d -> %s): %s", cardIndex, cardName, snd_strerror(err));
goto OnErrorExit;
}
diff --git a/plugins/alsa/alsa-utils-dump.c b/plugins/alsa/alsa-utils-dump.c
index b3bc864..9ff07d8 100644
--- a/plugins/alsa/alsa-utils-dump.c
+++ b/plugins/alsa/alsa-utils-dump.c
@@ -21,7 +21,6 @@
#include "alsa-softmixer.h"
-
PUBLIC char *AlsaDumpPcmUid(snd_pcm_t *pcmHandle, char *buffer, size_t len) {
snd_pcm_info_t *pcmInfo;
snd_pcm_info_alloca(&pcmInfo);
@@ -47,8 +46,8 @@ PUBLIC char *AlsaDumpCtlUid(snd_ctl_t *ctlHandle, char *buffer, size_t len) {
// retrieve PCM name for error/debug
int error = snd_ctl_card_info(ctlHandle, ctlInfo);
if (error) goto OnErrorExit;
-
- const char *ctlId = snd_ctl_card_info_get_id(ctlInfo);
+
+ const char *ctlId = snd_ctl_card_info_get_id(ctlInfo);
const char *ctlName = snd_ctl_card_info_get_name(ctlInfo);
snprintf(buffer, len, "hw:%s [%s]", ctlId, ctlName);
return buffer;
@@ -57,7 +56,6 @@ OnErrorExit:
return NULL;
}
-
PUBLIC void AlsaDumpFormats(CtlSourceT *source, snd_pcm_t *pcmHandle) {
char string[32];
snd_pcm_format_t format;
@@ -75,21 +73,20 @@ PUBLIC void AlsaDumpFormats(CtlSourceT *source, snd_pcm_t *pcmHandle) {
}
}
-
-PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) {
+PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) {
snd_ctl_card_info_t *cardInfo;
int err;
- int dev= -1;
+ int dev = -1;
snd_pcm_info_t *pcminfo;
snd_pcm_info_alloca(&pcminfo);
unsigned int subdevCount, subdevAvail;
-
+
snd_ctl_card_info_alloca(&cardInfo);
snd_ctl_card_info(handle, cardInfo);
int cardIndex = snd_ctl_card_info_get_card(cardInfo);
const char *cardId = snd_ctl_card_info_get_id(cardInfo);
const char *cardName = snd_ctl_card_info_get_name(cardInfo);
-
+
// loop on every sndcard devices
while (1) {
@@ -108,7 +105,7 @@ PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) {
continue;
}
- AFB_ApiNotice(source->api,"AlsaDumpCard card %d: %s [%s], device %d: %s [%s]",
+ AFB_ApiNotice(source->api, "AlsaDumpCard card %d: %s [%s], device %d: %s [%s]",
cardIndex, cardId, cardName, dev, snd_pcm_info_get_id(pcminfo), snd_pcm_info_get_name(pcminfo));
// loop on subdevices
@@ -128,10 +125,9 @@ PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) {
return;
OnErrorExit:
- return;
+ return;
}
-
PUBLIC void AlsaDumpPcmParams(CtlSourceT *source, snd_pcm_hw_params_t *pcmHwParams) {
snd_output_t *output;
char *buffer;
@@ -143,8 +139,7 @@ PUBLIC void AlsaDumpPcmParams(CtlSourceT *source, snd_pcm_hw_params_t *pcmHwPara
snd_output_close(output);
}
-
-PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, snd_pcm_t *pcm, const char* info) {
+PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, const char* info, snd_pcm_t *pcm) {
snd_output_t *out;
char *buffer;
@@ -159,46 +154,59 @@ PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, snd_pcm_t *pcm, const char* info
snd_output_close(out);
}
-PUBLIC void AlsaDumpCtlConfig(CtlSourceT *source, snd_config_t *config, int indent) {
+PUBLIC void AlsaDumpElemConfig(CtlSourceT *source, const char* info, const char* elem) {
+ snd_config_update();
+ snd_config_t *pcmConfig;
+ snd_config_search(snd_config, elem, &pcmConfig);
+ AlsaDumpCtlConfig(source, info, pcmConfig,1);
+}
+
+PUBLIC void AlsaDumpCtlConfig(CtlSourceT *source, const char* info, snd_config_t *config, int indent) {
snd_config_iterator_t it, next;
- // hugly hack to get minimalist indentation
- char *pretty = alloca(indent + 1);
+ // hugly hack to get minimalist indentation
+ char *pretty = alloca(indent + 1);
for (int idx = 0; idx < indent; idx++) pretty[idx] = '-';
- pretty[indent] = '\0';
-
- snd_config_for_each(it, next, config) {
- snd_config_t *node = snd_config_iterator_entry(it);
- const char *key;
-
- // ignore comment en empty lines
- if (snd_config_get_id(node, &key) < 0) continue;
-
- switch (snd_config_get_type(node)) {
- long valueI;
- const char *valueS;
-
- case SND_CONFIG_TYPE_INTEGER:
- snd_config_get_integer(node, &valueI);
- AFB_ApiNotice(source->api, "DumpAlsaConfig: %s %s: %d (int)", pretty, key, (int) valueI);
- break;
-
- case SND_CONFIG_TYPE_STRING:
- snd_config_get_string(node, &valueS);
- AFB_ApiNotice(source->api, "DumpAlsaConfig: %s %s: %s (str)", pretty, key, valueS);
- break;
-
- case SND_CONFIG_TYPE_COMPOUND:
- AFB_ApiNotice(source->api, "DumpAlsaConfig: %s %s { ", pretty, key);
- AlsaDumpCtlConfig(source, node, indent + 2);
- AFB_ApiNotice(source->api, "DumpAlsaConfig: %s } ", pretty);
- break;
-
- default:
- snd_config_get_string(node, &valueS);
- AFB_ApiNotice(source->api, "DumpAlsaConfig: %s: key=%s unknown=%s", pretty, key, valueS);
- break;
- }
+ pretty[indent] = '\0';
+
+ snd_config_for_each(it, next, config) {
+ snd_config_t *node = snd_config_iterator_entry(it);
+ const char *key;
+
+ // ignore comment en empty lines
+ if (snd_config_get_id(node, &key) < 0) continue;
+
+ switch (snd_config_get_type(node)) {
+ long valueI;
+ double valueD;
+ const char *valueS;
+
+ case SND_CONFIG_TYPE_INTEGER:
+ snd_config_get_integer(node, &valueI);
+ AFB_ApiNotice(source->api, "%s: %s %s: %d (int)", info, pretty, key, (int) valueI);
+ break;
+
+ case SND_CONFIG_TYPE_REAL:
+ snd_config_get_real(node, &valueD);
+ AFB_ApiNotice(source->api, "%s: %s %s: %.2f (float)", info, pretty, key, valueD);
+ break;
+
+ case SND_CONFIG_TYPE_STRING:
+ snd_config_get_string(node, &valueS);
+ AFB_ApiNotice(source->api, "%s: %s %s: %s (str)", info, pretty, key, valueS);
+ break;
+
+ case SND_CONFIG_TYPE_COMPOUND:
+ AFB_ApiNotice(source->api, "%s: %s %s { ", info, pretty, key);
+ AlsaDumpCtlConfig(source, info, node, indent + 2);
+ AFB_ApiNotice(source->api, "%s: %s } ", info, pretty);
+ break;
+
+ default:
+ snd_config_get_string(node, &valueS);
+ AFB_ApiNotice(source->api, "%s: %s: key=%s unknown=%s", info, pretty, key, valueS);
+ break;
}
+ }
}