diff options
28 files changed, 2268 insertions, 1762 deletions
@@ -57,7 +57,7 @@ Alsa snd-aloop impose '0' as playback device. Soft mixer will start from last su Current version does not handle audio rate conversion, using gstreamer or equivalent to match with audio hardware params is mandatory. ``` - gst123 --audio-output alsa=hw:XXX,0,7 $PROJECT_ROOT/conf.d/project/sounds/trio-divi-alkazabach.wav + gst123 --audio-output alsa=hw:Loopback,0,0 $PROJECT_ROOT/conf.d/project/sounds/trio-divi-alkazabach.mp3 gst123 --audio-output alsa=hw:XXX,0,??? other sound file @@ -73,4 +73,11 @@ situation. In order to clean up your Alsa snd-aloop config, a simple "rmmod" mig In case of doubt check with folling command that you start from a clear green field ``` rmmod snd-aloop && modprobe --first-time snd-aloop && amixer -D hw:Loopback controls | grep vol -```
\ No newline at end of file +``` + + +Work in Progress + + mise en place control pour master sur la carte playback/source + integration du cas d'un stream avec source non loop + test du rate converter
\ No newline at end of file diff --git a/conf.d/cmake/00-opensuse-osconfig.cmake b/conf.d/cmake/00-opensuse-osconfig.cmake index 9f8ce3d..3da2b4b 100644 --- a/conf.d/cmake/00-opensuse-osconfig.cmake +++ b/conf.d/cmake/00-opensuse-osconfig.cmake @@ -1,2 +1,4 @@ message(STATUS "*** Notice: OpenSuSe LUA-5.3+DynApi") list(APPEND PKG_REQUIRED_LIST lua>=5.3) + +set(USE_EFENCE 1) diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake index 536d4b5..8d6b4b7 100644 --- a/conf.d/cmake/config.cmake +++ b/conf.d/cmake/config.cmake @@ -18,7 +18,7 @@ # Project Info # ------------------ -set(PROJECT_NAME 4a-softmixer) +set(PROJECT_NAME 4a-smixer) set(PROJECT_PRETTY_NAME "Audio SoftMixer") set(PROJECT_DESCRIPTION "Soft Mixer for 4A (AGL Advanced Audio Architecture)") set(PROJECT_URL "https://github.com/iotbzh/4a-softmixer") @@ -199,7 +199,7 @@ set(AFB_REMPORT "1234" CACHE PATH "Default binder listening port") # Print a helper message when every thing is finished # ---------------------------------------------------- -set(CLOSING_MESSAGE "Typical binding launch: afb-daemon --name ${PROJECT_NAME}-afbd --port=${AFB_REMPORT} --workdir=${CMAKE_BINARY_DIR} --ldpath=/dev/null --binding=package/lib/softmixer-binding.so --roothttp=package/htdocs --token=\"${AFB_TOKEN}\" --tracereq=common --verbose") +set(CLOSING_MESSAGE "Typical binding launch: afb-daemon --name smixer-test-afbd --port=${AFB_REMPORT} --workdir=${CMAKE_BINARY_DIR} --ldpath=/dev/null --binding=package/lib/softmixer-binding.so --roothttp=package/htdocs --token=\"${AFB_TOKEN}\" --tracereq=common --verbose") set(PACKAGE_MESSAGE "Install widget file using in the target : afm-util install ${PROJECT_NAME}.wgt") # Optional schema validator about now only XML, LUA and JSON diff --git a/conf.d/project/etc/4a--softmixer-test.json b/conf.d/project/etc/smixer-test-config.json index d646e3b..d646e3b 100644 --- a/conf.d/project/etc/4a--softmixer-test.json +++ b/conf.d/project/etc/smixer-test-config.json diff --git a/conf.d/project/lua.d/softmixer-simple-test.lua b/conf.d/project/lua.d/softmixer-simple-test.lua index 115831b..87fb866 100644 --- a/conf.d/project/lua.d/softmixer-simple-test.lua +++ b/conf.d/project/lua.d/softmixer-simple-test.lua @@ -21,131 +21,221 @@ -- Static variables should be prefixed with _ _EventHandle={} --- Call when AlsaCore return HAL active list -function _AlsaPingCB_ (source, result, context) - AFB:notice (source, "--InLua-- PingCB: result='%s'", Dump_Table(result)) +-- make variable visible from ::OnExitError:: +local error +local result -end +local printf = function(s,...) + io.write(s:format(...)) + io.write("\n") + return +end -- Display receive arguments and echo them to caller function _mixer_simple_test_ (source, args) do - local error - local response + - -- ================== Default Alsa snd-aloop numid and subdev config - local aloop = { - ["devices"] = {["playback"]=0,["capture"]=1}, - ["ramps"] = { + -- Mixer UID is used as API name + + -- ==================== Default rate =========================== + + local audio_params ={ + defaults = { ["rate"] = 48000 }, + standard = { ["rate"] = 44100 }, + basic= { ["rate"] = 8000 }, + } + + local volume_ramps = { {["uid"]="ramp-fast", ["delay"]= 050, ["up"]=10,["down"]=3}, {["uid"]="ramp-slow", ["delay"]= 250, ["up"]=03,["down"]=1}, {["uid"]="ramp-normal", ["delay"]= 100, ["up"]=06,["down"]=2}, - }, + } + + -- ======================= Loop PCM =========================== + local snd_aloop = { + ["uid"] = "Alsa-Loop", + ["path"]= "/dev/snd/by-path/platform-snd_aloop.0", + ["devices"] = {["playback"]=0,["capture"]=1}, ["subdevs"] = { - {["subdev"]= 0, ["numid"]= 51}, - {["subdev"]= 1, ["numid"]= 57}, + {["subdev"]= 0, ["numid"]= 51, ["uid"]= "loop-legacy"}, + {["subdev"]= 1, ["numid"]= 57, ["uid"]= "loop-multimedia"}, {["subdev"]= 2, ["numid"]= 63}, {["subdev"]= 3, ["numid"]= 69}, {["subdev"]= 4, ["numid"]= 75}, {["subdev"]= 5, ["numid"]= 81}, {["subdev"]= 6, ["numid"]= 87}, {["subdev"]= 7, ["numid"]= 93}, - } + }, } - -- ==================== Default rate =========================== - - local audio_defaults = { - ["rate"] = 48000, - } - - -- ======================= Loop PCM =========================== - local snd_aloop = { - ["uid"] = "Alsa-Loop", - ["devpath"] = "/dev/snd/by-path/platform-snd_aloop.0", - ["params"] = audio_defaults, - ["ramps"] = aloop.ramps, - ["devices"] = aloop.devices, - ["subdevs"] = aloop.subdevs, - } + -- ============================= Backend (Sound Cards) =================== - -- ============================= Sound Cards =================== local snd_yamaha = { ["uid"]= "YAMAHA-APU70", - ["devpath"]= "/dev/snd/by-id/usb-YAMAHA_Corporation_YAMAHA_AP-U70_USB_Audio_00-00", - ["params"] = snd_params, + ["path"]= "/dev/snd/by-id/usb-YAMAHA_Corporation_YAMAHA_AP-U70_USB_Audio_00-00", + ["params"]= audio_params.default, ["sink"] = { - {["uid"]= "front-right", ["port"]= 0}, - {["uid"]= "front-left", ["port"]= 1}, + ["channels"] = { + {["uid"]= "front-right", ["port"]= 0}, + {["uid"]= "front-left", ["port"]= 1}, + }, } } - local snd_jabra= { - ["uid"]= "Jabra-Solemate", - ["devpath"]= "/dev/snd/by-id/usb-0b0e_Jabra_SOLEMATE_v1.34.0-00", - ["params"] = snd_params, + local snd_usb_8ch= { + ["uid"]= "8CH-USB", + ["path"]= "/dev/snd/by-id/usb-0d8c_USB_Sound_Device-00", + ["params"] = audio_params.default, ["sink"] = { - {["uid"]= "front-right", ["port"]= 0}, - {["uid"]= "front-left", ["port"]= 1}, + ["controls"]= { + ["volume"] = {["name"]= "Speaker Playback Volume", ["value"]=80}, + ["mute"] = {["name"]= "Speaker Playback Switch"}, + }, + ["channels"] = { + {["uid"]= "front-right", ["port"]= 0}, + {["uid"]= "front-left" , ["port"]= 1}, + {["uid"]= "middle-right", ["port"]= 2}, + {["uid"]= "middle-left" , ["port"]= 3}, + {["uid"]= "back-right", ["port"]= 4}, + {["uid"]= "back-left" , ["port"]= 5}, + {["uid"]= "centre-left" , ["port"]= 6}, + {["uid"]= "centre-left" , ["port"]= 7}, + }, + }, + ["source"] = { + ["controls"]= { + ["volume"] = {["name"]= "Capture Volume"}, + ["mute"] = {["name"]= "Capture Switch"}, + }, + ["channels"] = { + {["uid"]= "mic-right", ["port"]= 0}, + {["uid"]= "mic-left" , ["port"]= 1}, + }, } } -- ============================= Zones =================== + local zone_stereo={ + ["uid"] = "full-stereo", + ["sink"] = { + {["target"]="front-right",["channel"]=0}, + {["target"]="front-left" ,["channel"]=1}, + {["target"]="middle-right",["channel"]=0}, + {["target"]="middle-left" ,["channel"]=1}, + {["target"]="back-right",["channel"]=0}, + {["target"]="back-left" ,["channel"]=1}, + } + } + local zone_front= { ["uid"] = "front-seats", - ["type"] = "playback", - ["mapping"] = { + ["sink"] = { {["target"]="front-right",["channel"]=0}, {["target"]="front-left" ,["channel"]=1}, } } + local zone_middle= { + ["uid"] = "middle-seats", + ["sink"] = { + {["target"]="middle-right",["channel"]=0}, + {["target"]="middle-left" ,["channel"]=1}, + } + } + + local zone_back= { + ["uid"] = "back-seats", + ["sink"] = { + {["target"]="back-right",["channel"]=0}, + {["target"]="back-left" ,["channel"]=1}, + } + } + + local zone_driver= { + ["uid"] = "driver-seat", + ["source"] = { + {["target"]="mic-right",["channel"]=0}, + }, + ["sink"] = { + {["target"]="front-right",["channel"]=0}, + } + } + -- =================== Audio Stream ============================ local stream_music= { - ["uid"] = "multimedia", - ["zone"] = "front-seats", - ["ramp"] = "ramp-slow", + ["uid"] = "multimedia", + ["zone"]= "full-stereo", + ["source"]= "loop-multimedia", ["volume"]= 60, ["mute"] = false, + ["params"]= audio_params.standard, } local stream_navigation= { ["uid"] = "navigation", - ["zone"] = "front-seats", - ["ramp"] = "ramp-normal", - ["volume"]= 70, + ["zone"]= "front-seats", + ["volume"]= 60, ["mute"] = false, } - + local stream_emergency= { ["uid"] = "emergency", - ["zone"] = "front-seats", - ["ramp"] = "ramp-fast", + ["zone"] = "driver-seat", ["volume"]= 80, ["mute"] = false, + --["params"]= audio_params.basic, } + local stream_radio= { + ["uid"] = "radio", + ["zone"] = "full-stereo", + --["source"]= snd_usb_8ch.uid, + ["volume"]= 60, + ["mute"] = false, + } + + local stream_pulse= { + ["uid"] = "pulseaudio", + ["zone"] = "back-seats", + ["source"]= "loop-legacy", + ["volume"]= 60, + ["mute"] = false, + } + --- ================ Create Mixer ========================= - local MyMixer= { - ["uid"]="Simple_Mixer", - ["backend"] = {snd_yamaha}, - ["frontend"]= {snd_aloop}, - ["zones"] = {zone_front}, - ["streams"] = {stream_music,stream_navigation,stream_emergency}, + local MyTestHal= { + ["uid"]= "MyMixer", + ["ramps"]= volume_ramps, + ["playbacks"] = {snd_usb_8ch}, + ["captures"]= {snd_usb_8ch}, + ["loops"] = {snd_aloop}, + ["zones"] = {zone_stereo, zone_front, zone_back, zone_middle, zone_driver}, + ["streams"] = {stream_pulse, stream_music, stream_navigation }, + -- ["streams"] = {stream_pulse, stream_music, stream_navigation, stream_emergency, stream_radio }, + } - local error,response= smix:_mixer_new_ (source, MyMixer) + -- direct LUA call because controller cannot call its own API from AFB:servsync + error,result= smix:_mixer_new_ (source, {["uid"]="MyMixer"}) if (error ~= 0) then - AFB:error (source, "--InLua-- smix:_mixer_new_ fail config=%s", Dump_Table(aloop)) + AFB:error (source, "--InLua-- smix:_mixer_new_ fail config=%s", Dump_Table(result)) goto OnErrorExit else - AFB:notice (source, "--InLua-- smix:_mixer_new_ done response=%s\n", Dump_Table(response)) + AFB:notice (source, "--InLua-- smix:_mixer_new_ done\n") end + error,result= AFB:servsync(source, "MyMixer", "attach", MyTestHal) + if (error) then + AFB:error (source, "--InLua-- API MyMixer/attach fail error=%d", error) + goto OnErrorExit + else + AFB:notice (source, "--InLua-- MyMixer/attach done result=%s\n", Dump_Table(result)) + end -- ================== Happy End ============================= AFB:notice (source, "--InLua-- Test success") @@ -153,6 +243,11 @@ function _mixer_simple_test_ (source, args) -- ================= Unhappy End ============================ ::OnErrorExit:: - AFB:error (source, "--InLua-- Test fail") + local response=result["request"] + printf ("--InLua-- ------------STATUS= %s --------------", result["status"]) + printf ("--InLua-- ++ INFO= %s", Dump_Table(response["info"])) + printf ("--InLua-- ----------TEST %s-------------", result["status"]) + + AFB:error (source, "--InLua-- Test Fail") return 1 -- unhappy end -- end diff --git a/mixer-binding/mixer-binding.c b/mixer-binding/mixer-binding.c index 1d4be59..edfb408 100644 --- a/mixer-binding/mixer-binding.c +++ b/mixer-binding/mixer-binding.c @@ -117,9 +117,9 @@ PUBLIC int afbBindingVdyn(afb_dynapi *apiHandle) { const char *dirList= getenv("CONTROL_CONFIG_PATH"); if (!dirList) dirList=CONTROL_CONFIG_PATH; - const char *configPath = CtlConfigSearch(apiHandle, dirList, "4a-"); + const char *configPath = CtlConfigSearch(apiHandle, dirList, "smixer"); if (!configPath) { - AFB_ApiError(apiHandle, "CtlPreInit: No 4a-%s-* config found in %s ", GetBinderName(), dirList); + AFB_ApiError(apiHandle, "CtlPreInit: No smixer-%s-* config found in %s ", GetBinderName(), dirList); goto OnErrorExit; } diff --git a/nbproject/configurations.xml b/nbproject/configurations.xml index 812b398..187c42f 100644 --- a/nbproject/configurations.xml +++ b/nbproject/configurations.xml @@ -26,15 +26,17 @@ </df> <df name="plugins"> <df name="alsa"> - <in>alsa-api-backend.c</in> - <in>alsa-api-frontend.c</in> + <in>alsa-api-loop.c</in> + <in>alsa-api-pcm.c</in> + <in>alsa-api-ramp.c</in> + <in>alsa-api-sink.c</in> + <in>alsa-api-source.c</in> <in>alsa-api-streams.c</in> <in>alsa-api-zones.c</in> <in>alsa-capture.c</in> <in>alsa-core-ctl.c</in> <in>alsa-core-pcm.c</in> <in>alsa-plug-dmix.c</in> - <in>alsa-plug-multi.c</in> <in>alsa-plug-rate.c</in> <in>alsa-plug-route.c</in> <in>alsa-plug-vol.c</in> @@ -367,14 +369,35 @@ <cTool flags="1"> </cTool> </item> - <item path="plugins/alsa/alsa-api-backend.c" ex="false" tool="0" flavor2="2"> + <item path="plugins/alsa/alsa-api-loop.c" ex="false" tool="0" flavor2="2"> <cTool flags="0"> <incDir> <pElem>/usr/include/lua5.3</pElem> </incDir> </cTool> </item> - <item path="plugins/alsa/alsa-api-frontend.c" ex="false" tool="0" flavor2="2"> + <item path="plugins/alsa/alsa-api-pcm.c" ex="false" tool="0" flavor2="2"> + <cTool flags="0"> + <incDir> + <pElem>/usr/include/lua5.3</pElem> + </incDir> + </cTool> + </item> + <item path="plugins/alsa/alsa-api-ramp.c" ex="false" tool="0" flavor2="2"> + <cTool flags="0"> + <incDir> + <pElem>/usr/include/lua5.3</pElem> + </incDir> + </cTool> + </item> + <item path="plugins/alsa/alsa-api-sink.c" ex="false" tool="0" flavor2="2"> + <cTool flags="0"> + <incDir> + <pElem>/usr/include/lua5.3</pElem> + </incDir> + </cTool> + </item> + <item path="plugins/alsa/alsa-api-source.c" ex="false" tool="0" flavor2="2"> <cTool flags="0"> <incDir> <pElem>/usr/include/lua5.3</pElem> @@ -424,13 +447,6 @@ </incDir> </cTool> </item> - <item path="plugins/alsa/alsa-plug-multi.c" ex="false" tool="0" flavor2="2"> - <cTool flags="0"> - <incDir> - <pElem>/usr/include/lua5.3</pElem> - </incDir> - </cTool> - </item> <item path="plugins/alsa/alsa-plug-rate.c" ex="false" tool="0" flavor2="2"> <cTool flags="0"> <incDir> diff --git a/plugins/alsa/alsa-api-backend.c b/plugins/alsa/alsa-api-backend.c deleted file mode 100644 index 85d9566..0000000 --- a/plugins/alsa/alsa-api-backend.c +++ /dev/null @@ -1,220 +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" - -// Fulup need to be cleanup with new controller version -extern Lua2cWrapperT Lua2cWrap; - - - -STATIC int ProcessOneChannel(CtlSourceT *source, const char* uid, json_object *channelJ, AlsaPcmChannelT *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; -} - -PUBLIC int ProcessSndParams(CtlSourceT *source, const char* uid, json_object *paramsJ, AlsaPcmHwInfoT *params) { - const char *format = NULL, *access = NULL; - - // some default values - params->rate = ALSA_DEFAULT_PCM_RATE; - params->channels = 2; - params->sampleSize = 0; - - int error = wrap_json_unpack(paramsJ, "{s?i,s?i, s?s, s?s !}", "rate", ¶ms->rate, "channels", ¶ms->channels, "format", &format, "access", &access); - if (error) goto OnErrorExit; - - if (!format) params->format = SND_PCM_FORMAT_S16_LE; - else if (!strcasecmp(format, "S16_LE")) params->format = SND_PCM_FORMAT_S16_LE; - else if (!strcasecmp(format, "S16_BE")) params->format = SND_PCM_FORMAT_S16_BE; - else if (!strcasecmp(format, "U16_LE")) params->format = SND_PCM_FORMAT_U16_LE; - else if (!strcasecmp(format, "U16_BE")) params->format = SND_PCM_FORMAT_U16_BE; - else if (!strcasecmp(format, "S32_LE")) params->format = SND_PCM_FORMAT_S32_LE; - else if (!strcasecmp(format, "S32_BE")) params->format = SND_PCM_FORMAT_S32_BE; - else if (!strcasecmp(format, "U32_LE")) params->format = SND_PCM_FORMAT_U32_LE; - else if (!strcasecmp(format, "U32_BE")) params->format = SND_PCM_FORMAT_U32_BE; - else if (!strcasecmp(format, "S24_LE")) params->format = SND_PCM_FORMAT_S24_LE; - else if (!strcasecmp(format, "S24_BE")) params->format = SND_PCM_FORMAT_S24_BE; - else if (!strcasecmp(format, "U24_LE")) params->format = SND_PCM_FORMAT_U24_LE; - else if (!strcasecmp(format, "U24_BE")) params->format = SND_PCM_FORMAT_U24_BE; - else if (!strcasecmp(format, "S8")) params->format = SND_PCM_FORMAT_S8; - else if (!strcasecmp(format, "U8")) params->format = SND_PCM_FORMAT_U8; - else if (!strcasecmp(format, "FLOAT_LE")) params->format = SND_PCM_FORMAT_FLOAT_LE; - else if (!strcasecmp(format, "FLOAT_BE")) params->format = SND_PCM_FORMAT_FLOAT_LE; - else { - AFB_ApiNotice(source->api, "ProcessSndParams:%s(params) unsupported format 'S16_LE|S32_L|...' format=%s", uid, format); - goto OnErrorExit; - } - - if (!access) params->access = SND_PCM_ACCESS_RW_INTERLEAVED; - else if (!strcasecmp(access, "MMAP_INTERLEAVED")) params->access = SND_PCM_ACCESS_MMAP_INTERLEAVED; - else if (!strcasecmp(access, "MMAP_NONINTERLEAVED")) params->access = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; - else if (!strcasecmp(access, "MMAP_COMPLEX")) params->access = SND_PCM_ACCESS_MMAP_COMPLEX; - else if (!strcasecmp(access, "RW_INTERLEAVED")) params->access = SND_PCM_ACCESS_RW_INTERLEAVED; - else if (!strcasecmp(access, "RW_NONINTERLEAVED")) params->access = SND_PCM_ACCESS_RW_NONINTERLEAVED; - - else { - AFB_ApiNotice(source->api, "ProcessSndParams:%s(params) unsupported access 'RW_INTERLEAVED|MMAP_INTERLEAVED|MMAP_COMPLEX' access=%s", uid, access); - goto OnErrorExit; - } - - return 0; - -OnErrorExit: - return -1; -} - -STATIC int ProcessOneSndCard(CtlSourceT *source, json_object *sndcardJ, AlsaPcmInfoT *snd) { - json_object *sinkJ = NULL, *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", ¶msJ - ); - 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; - } - } else { - snd->params.rate = ALSA_DEFAULT_PCM_RATE; - snd->params.access = SND_PCM_ACCESS_RW_INTERLEAVED; - snd->params.format = SND_PCM_FORMAT_S16_LE; - snd->params.channels = 2; - } - - // 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 - char dmixUid[100]; - snprintf(dmixUid, sizeof (dmixUid), "Dmix-%s", snd->uid); - AlsaPcmInfoT *dmixPcm = AlsaCreateDmix(source, dmixUid, snd, 0); - if (!dmixPcm) { - AFB_ApiError(source->api, "ProcessOneSndCard: sndcard=%s fail to attach dmix plugin", snd->uid); - goto OnErrorExit; - } else { - snd->cardid = dmixPcm->cardid; - } - - switch (json_object_get_type(sinkJ)) { - case json_type_object: - snd->ccount = 1; - snd->channels = calloc(snd->ccount + 1, sizeof (AlsaPcmChannelT)); - 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 (AlsaPcmChannelT)); - 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; -} - -PUBLIC int SndBackend(CtlSourceT *source, json_object *argsJ) { - SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; - int error; - size_t count; - - assert(mixerHandle); - - if (mixerHandle->backend) { - AFB_ApiError(source->api, "SndBackend: mixer=%s backend already declared %s", mixerHandle->uid, json_object_get_string(argsJ)); - goto OnErrorExit; - } - - switch (json_object_get_type(argsJ)) { - case json_type_object: - count = 1; - mixerHandle->backend = calloc(count + 1, sizeof (AlsaPcmInfoT)); - error = ProcessOneSndCard(source, argsJ, &mixerHandle->backend[0]); - if (error) goto OnErrorExit; - break; - case json_type_array: - count = json_object_array_length(argsJ); - mixerHandle->backend = 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, &mixerHandle->backend[idx]); - if (error) goto OnErrorExit; - } - break; - default: - AFB_ApiError(source->api, "SndBackend: mixer=%s invalid argsJ= %s", mixerHandle->uid, json_object_get_string(argsJ)); - goto OnErrorExit; - } - - - if (count == 1) { - // only one sound card we multi would be useless - mixerHandle->multiPcm = &mixerHandle->backend[0]; - - } else { - - // instantiate an alsa multi plugin - mixerHandle->multiPcm = AlsaCreateMulti(source, "PcmMulti", 0); - if (!mixerHandle->multiPcm) goto OnErrorExit; - - } - return 0; - -OnErrorExit: - AFB_ApiNotice(source->api, "SndBackend mixer=%s fail to process: %s", mixerHandle->uid, json_object_get_string(argsJ)); - return -1; -} diff --git a/plugins/alsa/alsa-api-frontend.c b/plugins/alsa/alsa-api-frontend.c deleted file mode 100644 index 11b8a7e..0000000 --- a/plugins/alsa/alsa-api-frontend.c +++ /dev/null @@ -1,245 +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" -#include <string.h> - -// Fulup need to be cleanup with new controller version -extern Lua2cWrapperT Lua2cWrap; - -STATIC int ProcessOneRamp(CtlSourceT *source, const char* uid, json_object *rampJ, AlsaVolRampT *ramp) { - const char*rampUid; - - int error = wrap_json_unpack(rampJ, "{ss,si,si,si !}" - , "uid", &rampUid - , "delay", &ramp->delay - , "up", &ramp->stepUp - , "down", &ramp->stepDown - ); - if (error) goto OnErrorExit; - - ramp->delay=ramp->delay*100; // move from ms to us - ramp->uid = strdup(rampUid); - return 0; - -OnErrorExit: - AFB_ApiError(source->api, "ProcessOneRamp: sndcard=%s ramps: missing (uid||delay|up|down) json=%s", uid, json_object_get_string(rampJ)); - return -1; -} - -STATIC int ProcessOneSubdev(CtlSourceT *source, AlsaSndLoopT *loop, json_object *subdevJ, AlsaPcmHwInfoT *loopDefParams, AlsaPcmInfoT *subdev) { - json_object *paramsJ = NULL; - - int error = wrap_json_unpack(subdevJ, "{si,si,s?o !}", "subdev", &subdev->subdev, "numid", &subdev->numid, "params", ¶msJ); - if (error) { - AFB_ApiError(source->api, "ProcessOneSubdev: loop=%s missing (uid|subdev|numid) json=%s", loop->uid, json_object_get_string(subdevJ)); - goto OnErrorExit; - } - - if (paramsJ) { - error = ProcessSndParams(source, loop->uid, paramsJ, loopDefParams); - if (error) { - AFB_ApiError(source->api, "ProcessOneLoop: sndcard=%s invalid params=%s", loop->uid, json_object_get_string(paramsJ)); - goto OnErrorExit; - } - } else { - // use global loop params definition as default - memcpy(&subdev->params, loopDefParams, sizeof (AlsaPcmHwInfoT)); - } - // 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, *paramsJ = NULL, *rampsJ = NULL; - int error; - - error = wrap_json_unpack(loopJ, "{ss,s?s,s?s,s?i,s?o,so,s?o,s?o !}" - , "uid", &loop->uid - , "devpath", &loop->devpath - , "cardid", &loop->cardid - , "cardidx", &loop->cardidx - , "devices", &devicesJ - , "subdevs", &subdevsJ - , "params", ¶msJ - , "ramps", &rampsJ - ); - 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; - } - - AlsaPcmHwInfoT *loopDefParams = alloca(sizeof (AlsaPcmHwInfoT)); - if (paramsJ) { - error = ProcessSndParams(source, loop->uid, paramsJ, loopDefParams); - if (error) { - AFB_ApiError(source->api, "ProcessOneLoop: sndcard=%s invalid params=%s", loop->uid, json_object_get_string(paramsJ)); - goto OnErrorExit; - } - } else { - loopDefParams->rate = ALSA_DEFAULT_PCM_RATE; - loopDefParams->rate = ALSA_DEFAULT_PCM_RATE; - loopDefParams->access = SND_PCM_ACCESS_RW_INTERLEAVED; - loopDefParams->format = SND_PCM_FORMAT_S16_LE; - loopDefParams->channels = 2; - loopDefParams->sampleSize = 0; - } - - // Fake a sound card to check if loop is a valid Alsa snd driver - AlsaPcmInfoT sndLoop; - sndLoop.uid = loop->uid; - sndLoop.devpath = loop->devpath; - sndLoop.cardid = loop->cardid; - sndLoop.device = 0; - sndLoop.subdev = 0; - error = AlsaByPathDevid(source, &sndLoop); - if (error) { - AFB_ApiError(source->api, "ProcessOneLoop: loop=%s not found config=%s", loop->uid, json_object_get_string(loopJ)); - goto OnErrorExit; - } - loop->uid = sndLoop.uid; - loop->devpath = sndLoop.devpath; - loop->cardid = sndLoop.cardid; - loop->cardidx = sndLoop.cardidx; - loop->registry= calloc (1,sizeof(RegistryHandleT)); - - // process volume ramps - if (rampsJ) { - int rcount; - switch (json_object_get_type(rampsJ)) { - case json_type_object: - rcount = 1; - loop->ramps = calloc(rcount+1, sizeof (AlsaVolRampT)); - error = ProcessOneRamp(source, loop->uid, rampsJ, &loop->ramps[0]); - if (error) goto OnErrorExit; - break; - case json_type_array: - rcount = (int) json_object_array_length(rampsJ); - loop->ramps = calloc(rcount+1, sizeof (AlsaVolRampT)); - for (int idx = 0; idx < rcount; idx++) { - json_object *rampJ = json_object_array_get_idx(rampsJ, idx); - error = ProcessOneRamp(source, loop->uid, rampJ, &loop->ramps[idx]); - if (error) goto OnErrorExit; - } - break; - default: - AFB_ApiError(source->api, "ProcessOneLoop:%s invalid ramps=%s", loop->uid, json_object_get_string(rampsJ)); - goto OnErrorExit; - } - } - - // 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, loopDefParams, &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, loopDefParams, &loop->subdevs[idx]); - if (error) goto OnErrorExit; - } - break; - default: - AFB_ApiError(source->api, "ProcessOneLoop=%s invalid subdevs= %s", loop->uid, json_object_get_string(subdevsJ)); - goto OnErrorExit; - } - - return 0; - -OnErrorExit: - return -1; -} - -PUBLIC int SndFrontend(CtlSourceT *source, json_object *argsJ) { - SoftMixerHandleT *mixer = (SoftMixerHandleT*) source->context; - int error; - - assert(mixer); - - if (mixer->frontend) { - AFB_ApiError(source->api, "SndFrontend: mixer=%s SndFrontend already declared %s", mixer->uid, json_object_get_string(argsJ)); - goto OnErrorExit; - } - - mixer->frontend = calloc(1, sizeof (AlsaSndLoopT)); - - // or syntax purpose array is accepted but frontend should have a single driver entry - json_type type = json_object_get_type(argsJ); - if (type == json_type_array) { - size_t count = json_object_array_length(argsJ); - if (count != 1) { - AFB_ApiError(source->api, "SndFrontend: mixer=%s frontend only support on input driver args=%s", mixer->uid, json_object_get_string(argsJ)); - goto OnErrorExit; - } - argsJ = json_object_array_get_idx(argsJ, 0); - } - - type = json_object_get_type(argsJ); - if (type != json_type_object) { - AFB_ApiError(source->api, "SndFrontend: mixer=%s invalid object type= %s", mixer->uid, json_object_get_string(argsJ)); - goto OnErrorExit; - } - - error = ProcessOneLoop(source, argsJ, mixer->frontend); - if (error) { - AFB_ApiError(source->api, "SndFrontend: mixer=%s invalid object= %s", mixer->uid, json_object_get_string(argsJ)); - goto OnErrorExit; - } - - return 0; - -OnErrorExit: - return -1; -}
\ No newline at end of file diff --git a/plugins/alsa/alsa-api-loop.c b/plugins/alsa/alsa-api-loop.c new file mode 100644 index 0000000..9a746ea --- /dev/null +++ b/plugins/alsa/alsa-api-loop.c @@ -0,0 +1,205 @@ +/* + * 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> + +PUBLIC AlsaLoopSubdevT *ApiLoopFindSubdev(SoftMixerT *mixer, const char *streamUid, const char *targetUid, AlsaSndLoopT **loop) { + + // Either allocate a free loop subdev or search for a specific targetUid when specified + if (targetUid) { + for (int idx = 0; mixer->loops[idx]; idx++) { + for (int jdx = 0; jdx < mixer->loops[idx]->scount; jdx++) { + if (mixer->loops[idx]->subdevs[jdx]->uid && !strcasecmp(mixer->loops[idx]->subdevs[jdx]->uid, targetUid)) { + *loop = mixer->loops[idx]; + return mixer->loops[idx]->subdevs[jdx]; + } + } + } + } else { + for (int idx = 0; mixer->loops[idx]; idx++) { + for (int jdx = 0; mixer->loops[idx]->subdevs[jdx]; jdx++) { + if (!mixer->loops[idx]->subdevs[jdx]->uid) { + mixer->loops[idx]->subdevs[jdx]->uid = streamUid; + *loop = mixer->loops[idx]; + return mixer->loops[idx]->subdevs[jdx]; + } + } + } + } + return NULL; +} + +STATIC AlsaLoopSubdevT *ProcessOneSubdev(SoftMixerT *mixer, AlsaSndLoopT *loop, json_object *subdevJ) { + AlsaLoopSubdevT *subdev = calloc(1, sizeof (AlsaPcmCtlT)); + + int error = wrap_json_unpack(subdevJ, "{s?s, si,si,s?o !}" + , "uid", &subdev->uid + , "subdev", &subdev->index + , "numid", &subdev->numid + ); + if (error) { + AFB_ApiError(mixer->api, "ProcessOneSubdev: loop=%s missing (uid|subdev|numid) error=%s json=%s", loop->uid, wrap_json_get_error_string(error),json_object_get_string(subdevJ)); + goto OnErrorExit; + } + + // subdev with no UID are dynamically attached + if (subdev->uid) subdev->uid = strdup(subdev->uid); + + // create loop subdev entry point with cardidx+device+subdev in order to open subdev and not sndcard + AlsaDevInfoT loopSubdev; + loopSubdev.devpath=NULL; + loopSubdev.cardid=NULL; + loopSubdev.cardidx = loop->sndcard->cid.cardidx; + loopSubdev.device = loop->capture; + loopSubdev.subdev = subdev->index; + + // assert we may open this loopback subdev in capture mode + AlsaPcmCtlT *pcmInfo = AlsaByPathOpenPcm(mixer, &loopSubdev, SND_PCM_STREAM_CAPTURE); + if (!pcmInfo) goto OnErrorExit; + + // free PCM as we only open loop to assert it's a valid capture device + snd_pcm_close(pcmInfo->handle); + free(pcmInfo); + + return subdev; + +OnErrorExit: + return NULL; +} + +STATIC AlsaSndLoopT *AttachOneLoop(SoftMixerT *mixer, const char *uid, json_object *argsJ) { + AlsaSndLoopT *loop = calloc(1, sizeof (AlsaSndLoopT)); + json_object *subdevsJ = NULL, *devicesJ = NULL; + int error; + + loop->sndcard = (AlsaSndCtlT*) calloc(1, sizeof (AlsaSndCtlT)); + error = wrap_json_unpack(argsJ, "{ss,s?s,s?s,so,so !}" + , "uid", &loop->uid + , "path", &loop->sndcard->cid.devpath + , "cardid", &loop->sndcard->cid.cardid + , "devices", &devicesJ + , "subdevs", &subdevsJ + ); + if (error || !loop->uid || !subdevsJ || (!loop->sndcard->cid.devpath && !loop->sndcard->cid.cardid)) { + AFB_ApiNotice(mixer->api, "AttachOneLoop mixer=%s hal=%s missing 'uid|path|cardid|devices|subdevs' error=%s args=%s", mixer->uid, uid, wrap_json_get_error_string(error),json_object_get_string(argsJ)); + goto OnErrorExit; + } + + // try to open sound card control interface + loop->sndcard->ctl = AlsaByPathOpenCtl(mixer, loop->uid, loop->sndcard); + if (!loop->sndcard->ctl) { + AFB_ApiError(mixer->api, "AttachOneLoop mixer=%s hal=%s Fail open sndcard loop=%s devpath=%s cardid=%s (please check 'modprobe snd_aloop')", mixer->uid, uid, loop->uid, loop->sndcard->cid.devpath, loop->sndcard->cid.cardid); + goto OnErrorExit; + } + + // 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(mixer->api, "AttachOneLoop mixer=%s hal=%s Loop=%s missing 'capture|playback' error=%s devices=%s", mixer->uid, uid, loop->uid, wrap_json_get_error_string(error),json_object_get_string(devicesJ)); + goto OnErrorExit; + } + } + + switch (json_object_get_type(subdevsJ)) { + case json_type_object: + loop->scount = 1; + loop->subdevs = calloc(2, sizeof (void*)); + loop->subdevs[0] = ProcessOneSubdev(mixer, loop, subdevsJ); + if (!loop->subdevs[0]) goto OnErrorExit; + break; + case json_type_array: + loop->scount = (int) json_object_array_length(subdevsJ); + loop->subdevs = calloc(loop->scount + 1, sizeof (void*)); + for (int idx = 0; idx < loop->scount; idx++) { + json_object *subdevJ = json_object_array_get_idx(subdevsJ, idx); + loop->subdevs[idx] = ProcessOneSubdev(mixer, loop, subdevJ); + if (!loop->subdevs[idx]) goto OnErrorExit; + } + break; + default: + AFB_ApiError(mixer->api, "AttachOneLoop mixer=%s hal=%s Loop=%s invalid subdevs= %s", mixer->uid, uid, loop->uid, json_object_get_string(subdevsJ)); + goto OnErrorExit; + } + + // we may have to register up to 3 control per subdevice (vol, pause, actif) + loop->sndcard->registry = calloc(loop->scount * SMIXER_SUBDS_CTLS + 1, sizeof (RegistryEntryPcmT)); + loop->sndcard->rcount = loop->scount*SMIXER_SUBDS_CTLS; + + return loop; + +OnErrorExit: + return NULL; +} + +PUBLIC int ApiLoopAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ) { + + int index; + for (index = 0; index < mixer->max.loops; index++) { + if (!mixer->loops[index]) break; + } + + if (index == mixer->max.loops) { + AFB_ReqFailF(request, "too-small", "mixer=%s hal=%s max loop=%d argsJ= %s", mixer->uid, uid, mixer->max.loops, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + switch (json_object_get_type(argsJ)) { + size_t count; + + case json_type_object: + mixer->loops[index] = AttachOneLoop(mixer, uid, argsJ); + if (!mixer->loops[index]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s hal=%s invalid loop= %s", mixer->uid, uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + break; + + case json_type_array: + count = json_object_array_length(argsJ); + if (count > (mixer->max.loops - count)) { + AFB_ReqFailF(request, "too-small", "mixer=%s hal=%s max loop=%d argsJ= %s", mixer->uid, uid, mixer->max.loops, json_object_get_string(argsJ)); + goto OnErrorExit; + + } + + for (int idx = 0; idx < count; idx++) { + json_object *loopJ = json_object_array_get_idx(argsJ, idx); + mixer->loops[index + idx] = AttachOneLoop(mixer, uid, loopJ); + if (!mixer->loops[index + idx]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s hal=%s invalid loop= %s", mixer->uid, uid, json_object_get_string(loopJ)); + goto OnErrorExit; + } + } + break; + default: + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s hal=%s loops invalid argsJ= %s", mixer->uid, uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + return 0; + +OnErrorExit: + return -1; +}
\ No newline at end of file diff --git a/plugins/alsa/alsa-api-mixer.c b/plugins/alsa/alsa-api-mixer.c index b910b9f..300dd6a 100644 --- a/plugins/alsa/alsa-api-mixer.c +++ b/plugins/alsa/alsa-api-mixer.c @@ -23,186 +23,271 @@ #include <pthread.h> -// Fulup need to be cleanup with new controller version extern Lua2cWrapperT Lua2cWrap; -// API - -static void MixerApiVerbCB(AFB_ReqT request) { - json_object *valueJ, *backendJ = NULL, *frontendJ = NULL, *zonesJ = NULL, *streamsJ = NULL, *listJ = NULL; - // retrieve action handle from request and execute the request - json_object *argsJ = afb_request_json(request); - json_object *responseJ = json_object_new_object(); - - SoftMixerHandleT *mixer = (SoftMixerHandleT*) afb_request_get_vcbdata(request); +static void MixerRemoveVerb(AFB_ReqT request) { + SoftMixerT *mixer = (SoftMixerT*) afb_request_get_vcbdata(request); int error; - int delete = 0; - - CtlSourceT *source = alloca(sizeof (CtlSourceT)); - source->uid = mixer->uid; - source->api = request->dynapi; - source->request = request; - source->context = mixer; - - error = wrap_json_unpack(argsJ, "{s?b,s?o,s?o,s?o,s?o,s?o !}" - , "delete", &delete - , "list", &listJ - , "backend", &backendJ - , "frontend", &frontendJ - , "zones", &zonesJ - , "streams", &streamsJ - ); - if (error) { - AFB_ReqFailF(request, "invalid-syntax", "request missing 'uid|list|backend|frontend|zones|streams' mixer=%s", json_object_get_string(argsJ)); - goto OnErrorExit; - } - - // Free attached resources and free mixer - if (delete) { - for (int idx; mixer->streams[idx].uid; idx++) { - AlsaLoopStreamT *stream = &mixer->streams[idx]; - - AFB_ApiNotice(source->api, "cleaning mixer=%s stream=%s", mixer->uid, stream->uid); - - error = pthread_cancel(stream->copy.thread); - if (error) { - AFB_ReqFailF(request, "internal-error", "Fail to kill audio-stream threads mixer=%s", mixer->uid); - goto OnErrorExit; - } - - char apiStreamVerb[128]; - error = snprintf(apiStreamVerb, sizeof (apiStreamVerb), "%s/%s", mixer->uid, stream->uid); - if (error == sizeof (apiStreamVerb)) { - AFB_ApiError(source->api, "LoopStreams mixer=%s fail to Registry Stream API too long %s/%s", mixer->uid, mixer->uid, stream->uid); - goto OnErrorExit; - } - error = afb_dynapi_sub_verb(source->api, apiStreamVerb); - if (error) { - AFB_ApiError(source->api, "fail to Clean API verb=%s", apiStreamVerb); - goto OnErrorExit; - } + for (int idx; mixer->streams[idx]->uid; idx++) { + AlsaStreamAudioT *stream = mixer->streams[idx]; - // free audio-stream dynamic structures - snd_pcm_close(mixer->streams[idx].copy.pcmIn); - snd_pcm_close(mixer->streams[idx].copy.pcmOut); - if (stream->copy.evtsrc) sd_event_source_unref(stream->copy.evtsrc); - if (stream->copy.sdLoop) sd_event_unref(stream->copy.sdLoop); + AFB_ApiNotice(mixer->api, "cleaning mixer=%s stream=%s", mixer->uid, stream->uid); + error = pthread_cancel(stream->copy->thread); + if (error) { + AFB_ReqFailF(request, "internal-error", "Fail to kill audio-stream threads mixer=%s", mixer->uid); + goto OnErrorExit; } - // registry is attached to frontend - if (mixer->frontend->registry)free(mixer->frontend->registry); - - error = afb_dynapi_sub_verb(source->api, mixer->uid); + error = afb_dynapi_sub_verb(mixer->api, stream->uid); if (error) { - AFB_ApiError(source->api, "fail to Clean API verb=%s", mixer->uid); + AFB_ReqFailF(request, "internal-error", "Mixer=%s fail to remove verb=%s error=%s", mixer->uid, stream->uid, strerror(error)); goto OnErrorExit; } - // finally free mixer handle - free(mixer); - responseJ = json_object_new_string("Fulup: delete might not clean everything properly"); - goto OnSuccessExit; + // free audio-stream dynamic structures + snd_pcm_close(mixer->streams[idx]->copy->pcmIn); + snd_pcm_close(mixer->streams[idx]->copy->pcmOut); + if (stream->copy->evtsrc) sd_event_source_unref(stream->copy->evtsrc); + if (stream->copy->sdLoop) sd_event_unref(stream->copy->sdLoop); } - if (listJ) { - int streams = 0, quiet = 0, backend = 0, frontend = 0, zones = 0; + // // (Fulup to be Done) registry is attached to source + // if (mixer->sources) ApiSourcFree (mixer); + // if (mixer->sinks) ApiSinkFree (mixer); + // if (mixer->loops) ApiLoopFree (mixer); + // if (mixer->ramps) ApiRampFree (mixer); + // if (mixer->zones) ApiZoneFree (mixer); + + // finally free mixer handle + free(mixer); + AFB_ReqSucess(request, NULL, "Fulup: delete might not clean everything properly"); + + return; + +OnErrorExit: + AFB_ReqFail(request, "internal-error", "fail to delete mixer"); +} - error = wrap_json_unpack(listJ, "{s?b,s?b,s?b,s?b,s?b !}" +STATIC void MixerInfoVerb(AFB_ReqT request) { + + SoftMixerT *mixer = (SoftMixerT*) afb_request_get_vcbdata(request); + json_object *argsJ = afb_request_json(request); + int error, stream = 0, quiet = 0, backend = 0, source = 0, zones = 0; + + if (json_object_get_type(argsJ) == json_type_null) { + stream = 1; + } else { + error = wrap_json_unpack(argsJ, "{s?b,s?b,s?b,s?b,s?b !}" , "quiet", &quiet - , "streams", &streams + , "stream", &stream , "backend", &backend - , "frontend", &frontend + , "source", &source , "zones", &zones ); if (error) { - AFB_ReqFailF(request, "invalid-syntax", "list missing 'uid|backend|frontend|zones|streams' list=%s", json_object_get_string(listJ)); + AFB_ReqFailF(request, "invalid-syntax", "list missing 'quiet|stream|backend|source' argsJ=%s", json_object_get_string(argsJ)); goto OnErrorExit; } + } + json_object *responseJ = json_object_new_object(); - if (streams) { - streamsJ = json_object_new_array(); - - AlsaLoopStreamT *streams = mixer->streams; - for (int idx = 0; streams[idx].uid; idx++) { - if (quiet) { - json_object_array_add(streamsJ, json_object_new_string(streams[idx].uid)); - } else { - json_object *numidJ; - wrap_json_pack(&numidJ, "{si,si}" - , "volume", streams[idx].volume - , "mute", streams[idx].mute - ); - wrap_json_pack(&valueJ, "{ss,so}" - , "uid", streams[idx].uid - , "numid", numidJ - ); - json_object_array_add(streamsJ, valueJ); - AFB_ApiWarning(request->dynapi, "stream=%s", json_object_get_string(streamsJ)); - } - + if (stream) { + json_object *streamsJ = json_object_new_array(); + json_object *valueJ; + + AlsaStreamAudioT **streams = mixer->streams; + for (int idx = 0; streams[idx]->uid; idx++) { + if (quiet) { + json_object_array_add(streamsJ, json_object_new_string(streams[idx]->uid)); + } else { + json_object *numidJ; + wrap_json_pack(&numidJ, "{si,si}" + , "volume", streams[idx]->volume + , "mute", streams[idx]->mute + ); + wrap_json_pack(&valueJ, "{ss,so}" + , "uid", streams[idx]->uid + , "numid", numidJ + ); + json_object_array_add(streamsJ, valueJ); + AFB_ApiWarning(request->dynapi, "stream=%s", json_object_get_string(streamsJ)); } - json_object_object_add(responseJ, "streams", streamsJ); - } - if (backend || frontend || zones) { - AFB_ReqFailF(request, "not implemented", "(Fulup) list action Still To Be Done"); - goto OnErrorExit; } + json_object_object_add(responseJ, "streams", streamsJ); + } + + if (backend || source || zones) { + AFB_ReqFailF(request, "not implemented", "(Fulup) list action Still To Be Done"); + goto OnErrorExit; + } + + AFB_ReqSucess(request, responseJ, NULL); + return; + +OnErrorExit: + AFB_ReqFail(request, "internal-error", "fail to get mixer info"); + +} + +STATIC void MixerAttachVerb(AFB_ReqT request) { + SoftMixerT *mixer = (SoftMixerT*) afb_request_get_vcbdata(request); + const char *uid=NULL; + json_object *playbackJ = NULL, *captureJ = NULL, *zonesJ = NULL, *streamsJ = NULL, *rampsJ = NULL, *loopsJ = NULL; + json_object *argsJ = afb_request_json(request); + json_object *responseJ; + int error; + + error = wrap_json_unpack(argsJ, "{ss,s?o,s?o,s?o,s?o,s?o,s?o !}" + , "uid", &uid + , "ramps", &rampsJ + , "playbacks", &playbackJ + , "captures", &captureJ + , "loops", &loopsJ + , "zones", &zonesJ + , "streams", &streamsJ + ); + if (error) { + AFB_ApiError(mixer->api, "MixerAttachVerb: invalid-syntax mixer=%s error=%s args=%s", mixer->uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s missing 'uid|ramps|playbacks|captures|zones|streams' error=%s args=%s", mixer->uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); + goto OnErrorExit; + } - AFB_ReqSucess(request, responseJ, NULL); - return; + if (playbackJ) { + error = ApiSinkAttach(mixer, request, uid, playbackJ); + if (error) goto OnErrorExit; } - if (backendJ) { - error = SndBackend(source, backendJ); + if (captureJ) { + error = ApiSourceAttach(mixer, request, uid, captureJ); if (error) goto OnErrorExit; } - if (frontendJ) { - error = SndFrontend(source, frontendJ); + if (loopsJ) { + error = ApiLoopAttach(mixer, request, uid, loopsJ); if (error) goto OnErrorExit; } if (zonesJ) { - error = SndZones(source, zonesJ); + error = ApiZoneAttach(mixer, request, uid, zonesJ); + if (error) goto OnErrorExit; + } + + if (rampsJ) { + error = ApiRampAttach(mixer, request, uid, rampsJ); if (error) goto OnErrorExit; } if (streamsJ) { - error = LoopStreams(source, streamsJ, &responseJ); + error = ApiStreamAttach(mixer, request, uid, streamsJ, &responseJ); if (error) goto OnErrorExit; } -OnSuccessExit: - AFB_ReqSucess(request, responseJ, mixer->uid); + AFB_ReqNotice(request, "**** mixer=%s response=%s", json_object_get_string(responseJ), mixer->uid); + AFB_ReqSucess(request, NULL, mixer->uid); return; OnErrorExit: return; } +STATIC void MixerPingVerb(AFB_ReqT request) { + static int count = 0; + count++; + AFB_ReqNotice(request, "Controller:ping count=%d", count); + AFB_ReqSucess(request, json_object_new_int(count), NULL); + + return; +} + +STATIC void MixerEventCB(AFB_ApiT api, const char *evtLabel, struct json_object *eventJ) { + + SoftMixerT *mixer = (SoftMixerT*) afb_dynapi_get_userdata(api); + assert(mixer); + + AFB_ApiNotice(api, "Mixer=%s Received event=%s, eventJ=%s", mixer->uid, evtLabel, json_object_get_string(eventJ)); +} + +// Every HAL export the same API & Interface Mapping from SndCard to AudioLogic is done through alsaHalSndCardT +STATIC AFB_ApiVerbs CtrlApiVerbs[] = { + /* VERB'S NAME FUNCTION TO CALL SHORT DESCRIPTION */ + { .verb = "ping", .callback = MixerPingVerb, .info = "ping count test"}, + { .verb = "attach", .callback = MixerAttachVerb, .info = "attach resources to mixer"}, + { .verb = "remove", .callback = MixerRemoveVerb, .info = "remove existing mixer streams, zones, ..."}, + { .verb = "info", .callback = MixerInfoVerb, .info = "list existing mixer streams, zones, ..."}, + { .verb = NULL} /* marker for end of the array */ +}; + +STATIC int LoadStaticVerbs(SoftMixerT *mixer, AFB_ApiVerbs *verbs) { + int errcount = 0; + + for (int idx = 0; verbs[idx].verb; idx++) { + errcount += afb_dynapi_add_verb(mixer->api, CtrlApiVerbs[idx].verb, CtrlApiVerbs[idx].info, CtrlApiVerbs[idx].callback, (void*) mixer, CtrlApiVerbs[idx].auth, 0); + } + + return errcount; +}; + +STATIC int MixerInitCB(AFB_ApiT api) { + + SoftMixerT *mixer = (SoftMixerT*) afb_dynapi_get_userdata(api); + assert(mixer); + + // attach AFB mainloop to mixer + mixer->sdLoop = AFB_GetEventLoop(api); + + + AFB_ApiNotice(api, "MixerInitCB API=%s activated info=%s", mixer->uid, mixer->info); + + return 0; +} + +STATIC int MixerApiCB(void* handle, AFB_ApiT api) { + SoftMixerT *mixer = (SoftMixerT*) handle; + + mixer->api= api; + afb_dynapi_set_userdata(api, mixer); + afb_dynapi_on_event(api, MixerEventCB); + afb_dynapi_on_init(api, MixerInitCB); + + int error = LoadStaticVerbs(mixer, CtrlApiVerbs); + if (error) goto OnErrorExit; + + return 0; + +OnErrorExit: + return -1; +} + CTLP_LUA2C(_mixer_new_, source, argsJ, responseJ) { - SoftMixerHandleT *mixer = calloc(1, sizeof (SoftMixerHandleT)); - json_object *backendJ = NULL, *frontendJ = NULL, *zonesJ = NULL, *streamsJ = NULL; + SoftMixerT *mixer = calloc(1, sizeof (SoftMixerT)); int error; - assert(source->api); + mixer->max.loops = SMIXER_DEFLT_RAMPS; + mixer->max.sinks = SMIXER_DEFLT_SINKS; + mixer->max.sources = SMIXER_DEFLT_SOURCES; + mixer->max.zones = SMIXER_DEFLT_ZONES; + mixer->max.streams = SMIXER_DEFLT_STREAMS; + mixer->max.ramps = SMIXER_DEFLT_RAMPS; if (json_object_get_type(argsJ) != json_type_object) { AFB_ApiError(source->api, "_mixer_new_: invalid object type= %s", json_object_get_string(argsJ)); goto OnErrorExit; } - error = wrap_json_unpack(argsJ, "{ss,s?s,s?o,s?o,s?o,s?o !}" + error = wrap_json_unpack(argsJ, "{ss,s?s,s?i,s?i,s?i,s?i,s?i,s?i !}" , "uid", &mixer->uid , "info", &mixer->info - , "backend", &backendJ - , "frontend", &frontendJ - , "zones", &zonesJ - , "streams", &streamsJ); + , "max_loop", &mixer->max.loops + , "max_sink", &mixer->max.sinks + , "max_source", &mixer->max.sources + , "max_zone", &mixer->max.zones + , "max_stream", &mixer->max.streams + , "max_ramp", &mixer->max.ramps + ); if (error) { - AFB_ApiNotice(source->api, "_mixer_new_ missing 'uid|backend|frontend|zones|streams' mixer=%s", json_object_get_string(argsJ)); + AFB_ApiNotice(source->api, "_mixer_new_ missing 'uid|max_loop|max_sink|max_source|max_zone|max_stream|max_ramp' error=%s mixer=%s", wrap_json_get_error_string(error),json_object_get_string(argsJ)); goto OnErrorExit; } @@ -210,34 +295,18 @@ CTLP_LUA2C(_mixer_new_, source, argsJ, responseJ) { mixer->uid = strdup(mixer->uid); if (mixer->info)mixer->info = strdup(mixer->info); + mixer->loops = calloc(mixer->max.loops+1, sizeof (AlsaSndLoopT)); + mixer->sinks = calloc(mixer->max.sinks+1, sizeof (AlsaSndPcmT)); + mixer->sources = calloc(mixer->max.sources+1, sizeof (AlsaSndPcmT)); + mixer->zones = calloc(mixer->max.zones+1, sizeof (AlsaSndZoneT)); + mixer->streams = calloc(mixer->max.streams+1, sizeof (AlsaStreamAudioT)); + mixer->ramps = calloc(mixer->max.ramps+1, sizeof (AlsaVolRampT)); + // create mixer verb within API. - error = afb_dynapi_add_verb(source->api, mixer->uid, mixer->info, MixerApiVerbCB, mixer, NULL, 0); + error = afb_dynapi_new_api(source->api, mixer->uid, mixer->info, !MAINLOOP_CONCURENCY, MixerApiCB, mixer); if (error) { AFB_ApiError(source->api, "_mixer_new_ mixer=%s fail to Registry API verb", mixer->uid); - return -1; - } - - // make sure sub command get access to mixer handle - source->context = mixer; - - if (backendJ) { - error = SndBackend(source, backendJ); - if (error) goto OnErrorExit; - } - - if (frontendJ) { - error = SndFrontend(source, frontendJ); - if (error) goto OnErrorExit; - } - - if (zonesJ) { - error = SndZones(source, zonesJ); - if (error) goto OnErrorExit; - } - - if (streamsJ) { - error = LoopStreams(source, streamsJ, responseJ); - if (error) goto OnErrorExit; + goto OnErrorExit; } return 0; diff --git a/plugins/alsa/alsa-api-pcm.c b/plugins/alsa/alsa-api-pcm.c new file mode 100644 index 0000000..228b7a7 --- /dev/null +++ b/plugins/alsa/alsa-api-pcm.c @@ -0,0 +1,304 @@ +/* + * 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 <math.h> + +// move from vol % to absolute value +#define CONVERT_RANGE(val, min, max) ceil((val) * ((max) - (min)) * 0.01 + (min)) +#define CONVERT_VOLUME(val, min, max) (int) CONVERT_RANGE ((double)val, (double)min, (double)max) + + +STATIC AlsaPcmChannelT *ProcessOneChannel(SoftMixerT *mixer, const char *uid, json_object *argsJ) { + AlsaPcmChannelT *channel = calloc(1, sizeof (AlsaPcmChannelT)); + int error = wrap_json_unpack(argsJ, "{ss,si !}", "uid", &channel->uid, "port", &channel->port); + if (error) goto OnErrorExit; + + channel->uid = strdup(channel->uid); + return channel; + +OnErrorExit: + AFB_ApiError(mixer->api, "ProcessOneChannel: sndcard=%s channel: missing (uid||port) error=%s json=%s", uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); + free(channel); + return NULL; +} + +STATIC int ProcessOneControl(SoftMixerT *mixer, AlsaSndPcmT* pcm, json_object *argsJ, AlsaSndControlT *control) { + snd_ctl_elem_id_t* elemId = NULL; + snd_ctl_elem_info_t *elemInfo; + int numid = 0; + long value = ALSA_DEFAULT_PCM_VOLUME; + const char *name; + + + int error = wrap_json_unpack(argsJ, "{s?i,s?s,s?i !}" + , "numid", &numid + , "name", &name + , "value", &value + ); + if (error || (!numid && !name)) { + AFB_ApiError(mixer->api, "ProcessOneControl: sndcard=%s channel: missing (numid|name|value) error=%s json=%s", pcm->uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); + goto OnErrorExit; + } + + if (numid > 0) { + elemId = AlsaCtlGetNumidElemId(mixer, pcm->sndcard, numid); + if (!elemId) { + AFB_ApiError(mixer->api, "ProcessOneControl sndard=%s fail to find control numid=%d", pcm->sndcard->cid.cardid, numid); + goto OnErrorExit; + } + + } else { + elemId = AlsaCtlGetNameElemId(mixer, pcm->sndcard, name); + if (!elemId) { + AFB_ApiError(mixer->api, "ProcessOneControl sndard=%s fail to find control name=%s", pcm->sndcard->cid.cardid, name); + goto OnErrorExit; + } + } + + snd_ctl_elem_info_alloca(&elemInfo); + snd_ctl_elem_info_set_id(elemInfo, elemId); + control->name = strdup(snd_ctl_elem_info_get_name(elemInfo)); + control->numid = snd_ctl_elem_info_get_numid(elemInfo); + + if (snd_ctl_elem_info(pcm->sndcard->ctl, elemInfo) < 0) { + AFB_ApiError(mixer->api, "ProcessOneControl: sndard=%s numid=%d name='%s' not loadable", pcm->sndcard->cid.cardid, control->numid, control->name); + goto OnErrorExit; + } + + if (!snd_ctl_elem_info_is_writable(elemInfo)) { + AFB_ApiError(mixer->api, "ProcessOneControl: sndard=%s numid=%d name='%s' not writable", pcm->sndcard->cid.cardid, control->numid, control->name); + goto OnErrorExit; + } + + control->count = snd_ctl_elem_info_get_count(elemInfo); + switch (snd_ctl_elem_info_get_type(elemInfo)) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + control->min = 0; + control->max = 1; + control->step = 0; + error = CtlElemIdSetLong(mixer, pcm->sndcard, elemId, value); + break; + + case SND_CTL_ELEM_TYPE_INTEGER: + case SND_CTL_ELEM_TYPE_INTEGER64: + control->min = snd_ctl_elem_info_get_min(elemInfo); + control->max = snd_ctl_elem_info_get_max(elemInfo); + control->step = snd_ctl_elem_info_get_step(elemInfo); + error = CtlElemIdSetLong(mixer, pcm->sndcard, elemId, (int) CONVERT_VOLUME(value, control->min, control->max)); + break; + + default: + AFB_ApiError(mixer->api, "ProcessOneControl: sndard=%s numid=%d name='%s' invalid/unsupported type=%d", pcm->sndcard->cid.cardid, control->numid, control->name, snd_ctl_elem_info_get_type(elemInfo)); + goto OnErrorExit; + } + + if (error) { + AFB_ApiError(mixer->api, "ProcessOneControl: sndard=%s numid=%d name='%s' not writable", pcm->sndcard->cid.cardid, control->numid, control->name); + goto OnErrorExit; + } + + free(elemId); + + return 0; + +OnErrorExit: + if (elemId)free(elemId); + return -1; +} + +PUBLIC AlsaPcmHwInfoT *ApiPcmSetParams(SoftMixerT *mixer, const char *uid, json_object *paramsJ) { + AlsaPcmHwInfoT *params = calloc(1, sizeof (AlsaPcmHwInfoT)); + const char *format = NULL, *access = NULL; + + // some default values + params->rate = ALSA_DEFAULT_PCM_RATE; + params->channels = 2; + params->sampleSize = 0; + + if (paramsJ) { + int error = wrap_json_unpack(paramsJ, "{s?i,s?i, s?s, s?s !}", "rate", ¶ms->rate, "channels", ¶ms->channels, "format", &format, "access", &access); + if (error) { + AFB_ApiError(mixer->api, "ApiPcmSetParams: sndcard=%s invalid params=%s", uid, json_object_get_string(paramsJ)); + goto OnErrorExit; + } + } + + if (!format) { + params->format = SND_PCM_FORMAT_S16_LE; + params->formatS = "S16_LE"; + } else { + params->formatS = strdup(format); + if (!strcasecmp(format, "S16_LE")) params->format = SND_PCM_FORMAT_S16_LE; + else if (!strcasecmp(format, "S16_BE")) params->format = SND_PCM_FORMAT_S16_BE; + else if (!strcasecmp(format, "U16_LE")) params->format = SND_PCM_FORMAT_U16_LE; + else if (!strcasecmp(format, "U16_BE")) params->format = SND_PCM_FORMAT_U16_BE; + else if (!strcasecmp(format, "S32_LE")) params->format = SND_PCM_FORMAT_S32_LE; + else if (!strcasecmp(format, "S32_BE")) params->format = SND_PCM_FORMAT_S32_BE; + else if (!strcasecmp(format, "U32_LE")) params->format = SND_PCM_FORMAT_U32_LE; + else if (!strcasecmp(format, "U32_BE")) params->format = SND_PCM_FORMAT_U32_BE; + else if (!strcasecmp(format, "S24_LE")) params->format = SND_PCM_FORMAT_S24_LE; + else if (!strcasecmp(format, "S24_BE")) params->format = SND_PCM_FORMAT_S24_BE; + else if (!strcasecmp(format, "U24_LE")) params->format = SND_PCM_FORMAT_U24_LE; + else if (!strcasecmp(format, "U24_BE")) params->format = SND_PCM_FORMAT_U24_BE; + else if (!strcasecmp(format, "S8")) params->format = SND_PCM_FORMAT_S8; + else if (!strcasecmp(format, "U8")) params->format = SND_PCM_FORMAT_U8; + else if (!strcasecmp(format, "FLOAT_LE")) params->format = SND_PCM_FORMAT_FLOAT_LE; + else if (!strcasecmp(format, "FLOAT_BE")) params->format = SND_PCM_FORMAT_FLOAT_LE; + else { + AFB_ApiNotice(mixer->api, "ApiPcmSetParams:%s(params) unsupported format 'S16_LE|S32_L|...' format=%s", uid, format); + goto OnErrorExit; + } + } + + + if (!access) params->access = SND_PCM_ACCESS_RW_INTERLEAVED; + else if (!strcasecmp(access, "MMAP_INTERLEAVED")) params->access = SND_PCM_ACCESS_MMAP_INTERLEAVED; + else if (!strcasecmp(access, "MMAP_NONINTERLEAVED")) params->access = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; + else if (!strcasecmp(access, "MMAP_COMPLEX")) params->access = SND_PCM_ACCESS_MMAP_COMPLEX; + else if (!strcasecmp(access, "RW_INTERLEAVED")) params->access = SND_PCM_ACCESS_RW_INTERLEAVED; + else if (!strcasecmp(access, "RW_NONINTERLEAVED")) params->access = SND_PCM_ACCESS_RW_NONINTERLEAVED; + + else { + AFB_ApiNotice(mixer->api, "ApiPcmSetParams:%s(params) unsupported access 'RW_INTERLEAVED|MMAP_INTERLEAVED|MMAP_COMPLEX' access=%s", uid, access); + goto OnErrorExit; + } + + return params; + +OnErrorExit: + free(params); + return NULL; +} + +PUBLIC AlsaSndPcmT *ApiPcmAttachOne(SoftMixerT *mixer, const char *uid, snd_pcm_stream_t direction, json_object *argsJ) { + AlsaSndPcmT *pcm = calloc(1, sizeof (AlsaSndPcmT)); + json_object *sourceJ = NULL, *paramsJ = NULL, *sinkJ = NULL, *targetJ; + int error; + + pcm->sndcard = (AlsaSndCtlT*) calloc(1, sizeof (AlsaSndCtlT)); + error = wrap_json_unpack(argsJ, "{ss,s?s,s?s,s?i,s?i,s?o,s?o,s?o !}" + , "uid", &pcm->uid + , "path", &pcm->sndcard->cid.devpath + , "cardid", &pcm->sndcard->cid.cardid + , "device", &pcm->sndcard->cid.device + , "subdev", &pcm->sndcard->cid.subdev + , "sink", &sinkJ + , "source", &sourceJ + , "params", ¶msJ + ); + if (error) { + AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s missing 'uid|path|cardid|device|sink|source|params' error=%s args=%s", uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); + goto OnErrorExit; + } + + // try to open sound card control interface + pcm->sndcard->ctl = AlsaByPathOpenCtl(mixer, pcm->uid, pcm->sndcard); + if (!pcm->sndcard->ctl) { + AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s Fail to open sndcard uid=%s devpath=%s cardid=%s", uid, pcm->uid, pcm->sndcard->cid.devpath, pcm->sndcard->cid.cardid); + goto OnErrorExit; + } + + // check sndcard accepts params + pcm->sndcard->params = ApiPcmSetParams(mixer, pcm->uid, paramsJ); + if (!pcm->sndcard->params) { + AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s Fail to set params sndcard uid=%s params=%s", uid, pcm->uid, json_object_get_string(paramsJ)); + goto OnErrorExit; + } + + if (direction == SND_PCM_STREAM_PLAYBACK) { + if (!sinkJ) { + AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s SND_PCM_STREAM_PLAYBACK require sinks args=%s", uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + targetJ = sinkJ; + } + + if (direction == SND_PCM_STREAM_CAPTURE) { + if (!sourceJ) { + AFB_ApiError(mixer->api, "ApiPcmAttachOne: hal=%s SND_PCM_STREAM_CAPTURE require sources args=%s", uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + targetJ = sourceJ; + + // we may have to register SMIXER_SUBDS_CTLS per subdev (Fulup ToBeDone when sndcard get multiple device/subdev) + pcm->sndcard->registry = calloc(SMIXER_SUBDS_CTLS+1, sizeof (RegistryEntryPcmT)); + pcm->sndcard->rcount = SMIXER_SUBDS_CTLS; + } + + json_object *channelsJ = NULL, *controlsJ = NULL; + error = wrap_json_unpack(targetJ, "{so,s?o !}" + , "channels", &channelsJ + , "controls", &controlsJ + ); + if (error) { + AFB_ApiNotice(mixer->api, "ApiPcmAttachOne: hal=%s pcms missing channels|[controls] error=%s paybacks=%s", uid, wrap_json_get_error_string(error), json_object_get_string(argsJ)); + goto OnErrorExit; + } + if (channelsJ) { + switch (json_object_get_type(channelsJ)) { + + case json_type_object: + pcm->ccount = 1; + pcm->channels = calloc(2, sizeof (void*)); + pcm->channels[0] = ProcessOneChannel(mixer, pcm->uid, channelsJ); + if (!pcm->channels[0]) goto OnErrorExit; + break; + case json_type_array: + pcm->ccount = (int) json_object_array_length(channelsJ); + pcm->channels = calloc(pcm->ccount + 1, sizeof (void*)); + for (int idx = 0; idx < pcm->ccount; idx++) { + json_object *channelJ = json_object_array_get_idx(channelsJ, idx); + pcm->channels[idx] = ProcessOneChannel(mixer, pcm->uid, channelJ); + if (!pcm->channels[idx]) goto OnErrorExit; + } + break; + default: + AFB_ApiError(mixer->api, "ProcessPcmControls:%s invalid pcm=%s", pcm->uid, json_object_get_string(channelsJ)); + goto OnErrorExit; + } + } + + if (controlsJ) { + json_object *volJ = NULL, *muteJ = NULL; + error = wrap_json_unpack(controlsJ, "{s?o,s?o !}" + , "volume", &volJ + , "mute", &muteJ + ); + if (error) { + AFB_ApiNotice(mixer->api, "ProcessPcmControls: source missing [volume]|[mute] error=%s control=%s", wrap_json_get_error_string(error), json_object_get_string(controlsJ)); + goto OnErrorExit; + } + + if (volJ) error += ProcessOneControl(mixer, pcm, volJ, &pcm->volume); + if (muteJ) error += ProcessOneControl(mixer, pcm, muteJ, &pcm->mute); + if (error) goto OnErrorExit; + } + + // free useless resource and secure others + pcm->uid = strdup(pcm->uid); + + return pcm; + +OnErrorExit: + free(pcm); + return NULL; +} + diff --git a/plugins/alsa/alsa-api-ramp.c b/plugins/alsa/alsa-api-ramp.c new file mode 100644 index 0000000..002aec0 --- /dev/null +++ b/plugins/alsa/alsa-api-ramp.c @@ -0,0 +1,114 @@ +/* + * 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> + +// Set stream volume control in % +#define VOL_CONTROL_MAX 100 +#define VOL_CONTROL_MIN 0 +#define VOL_CONTROL_STEP 1 + +PUBLIC AlsaVolRampT *ApiRampGetByUid(SoftMixerT *mixer, const char *uid) { + AlsaVolRampT *ramp = NULL; + + // Loop on every Register zone pcm and extract (cardid) from (uid) + for (int idx = 0; mixer->ramps[idx]->uid != NULL; idx++) { + if (!strcasecmp(mixer->ramps[idx]->uid, uid)) { + ramp = mixer->ramps[idx]; + return ramp; + } + } + return NULL; +} + +STATIC AlsaVolRampT *AttachOneRamp(SoftMixerT *mixer, const char *uid, json_object *rampJ) { + const char*rampUid; + AlsaVolRampT *ramp = calloc(1, sizeof (AlsaVolRampT)); + + int error = wrap_json_unpack(rampJ, "{ss,si,si,si !}" + , "uid", &rampUid + , "delay", &ramp->delay + , "up", &ramp->stepUp + , "down", &ramp->stepDown + ); + if (error) { + AFB_ApiError(mixer->api, "AttachOneRamp mixer=%s hal=%s error=%s json=%s", mixer->uid, uid, wrap_json_get_error_string(error), json_object_get_string(rampJ)); + goto OnErrorExit; + } + + ramp->delay = ramp->delay * 100; // move from ms to us + ramp->uid = strdup(rampUid); + return ramp; + +OnErrorExit: + free(ramp); + return NULL; +} + +PUBLIC int ApiRampAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object *argsJ) { + int index; + + for (index = 0; index < mixer->max.ramps; index++) { + if (!mixer->ramps[index]) break; + } + + if (index == mixer->max.ramps) { + AFB_ReqFailF(request, "too-small", "mixer=%s hal=%s max ramp=%d argsJ= %s", mixer->uid, uid, mixer->max.ramps, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + switch (json_object_get_type(argsJ)) { + long count; + + case json_type_object: + mixer->ramps[index] = AttachOneRamp(mixer, uid, argsJ); + if (!mixer->ramps[index]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s hal=%s invalid ramp= %s", mixer->uid, uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + break; + + case json_type_array: + count = json_object_array_length(argsJ); + if (count > (mixer->max.ramps - count)) { + AFB_ReqFailF(request, "too-small", "mixer=%s hal=%s max ramp=%d argsJ= %s", mixer->uid, uid, mixer->max.ramps, json_object_get_string(argsJ)); + goto OnErrorExit; + + } + + for (int idx = 0; idx < count; idx++) { + json_object *streamAudioJ = json_object_array_get_idx(argsJ, idx); + mixer->ramps[index + idx] = AttachOneRamp(mixer, uid, streamAudioJ); + if (!mixer->ramps[index + idx]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s hal=%s invalid ramp= %s", mixer->uid, uid, json_object_get_string(streamAudioJ)); + goto OnErrorExit; + } + } + break; + default: + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s hal=%s ramps invalid argsJ= %s", mixer->uid, uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + return 0; + +OnErrorExit: + return -1; +} diff --git a/plugins/alsa/alsa-api-sink.c b/plugins/alsa/alsa-api-sink.c new file mode 100644 index 0000000..ab32276 --- /dev/null +++ b/plugins/alsa/alsa-api-sink.c @@ -0,0 +1,111 @@ +/* + * 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" + +PUBLIC AlsaPcmHwInfoT *ApiSinkGetParamsByZone(SoftMixerT *mixer, const char *target) { + + AlsaSndZoneT *zone = ApiZoneGetByUid(mixer, target); + if (!zone || !zone->sinks) return NULL; + + // use 1st channel to find attached sound card. + const char *channel = zone->sinks[0]->uid; + + // search for channel uid into mixer sinks + for (int idx = 0; mixer->sinks[idx]; idx++) { + for (int jdx = 0; jdx < mixer->sinks[idx]->ccount; jdx++) { + if (mixer->sinks[idx]->channels[jdx]->uid && !strcasecmp(channel, mixer->sinks[idx]->channels[jdx]->uid)) { + return mixer->sinks[idx]->sndcard->params; + } + } + } + return NULL; +} + +PUBLIC int ApiSinkAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ) { + + int index; + for (index = 0; index < mixer->max.sinks; index++) { + if (!mixer->sinks[index]) break; + } + + if (index == mixer->max.sinks) { + AFB_ReqFailF(request, "too-small", "mixer=%s max sink=%d argsJ= %s", mixer->uid, mixer->max.sinks, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + switch (json_object_get_type(argsJ)) { + long count; + char *dmixUid; + AlsaPcmCtlT* dmixConfig; + + case json_type_object: + mixer->sinks[index] = ApiPcmAttachOne(mixer, uid, SND_PCM_STREAM_PLAYBACK, argsJ); + if (!mixer->sinks[index]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s invalid sink= %s", mixer->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + // move from hardware to DMIX attach to sndcard + (void) asprintf(&dmixUid, "dmix-%s", mixer->sinks[index]->uid); + + dmixConfig = AlsaCreateDmix(mixer, dmixUid, mixer->sinks[index], 0); + if (!dmixConfig) { + AFB_ReqFailF(request, "internal-error", "mixer=%s sink=%s fail to create DMIX config", mixer->uid, mixer->sinks[index]->uid); + goto OnErrorExit; + } + + break; + + case json_type_array: + count = json_object_array_length(argsJ); + if (count > (mixer->max.sinks - count)) { + AFB_ReqFailF(request, "too-small", "mixer=%s max sink=%d argsJ= %s", mixer->uid, mixer->max.sinks, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + for (int idx = 0; idx < count; idx++) { + json_object *sinkJ = json_object_array_get_idx(argsJ, idx); + mixer->sinks[index + idx] = ApiPcmAttachOne(mixer, uid, SND_PCM_STREAM_PLAYBACK, sinkJ); + if (!mixer->sinks[index + idx]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s invalid sink= %s", mixer->uid, json_object_get_string(sinkJ)); + goto OnErrorExit; + } + + // move from hardware to DMIX attach to sndcard + (void) asprintf(&dmixUid, "dmix-%s", mixer->sinks[index]->uid); + + dmixConfig = AlsaCreateDmix(mixer, dmixUid, mixer->sinks[index], 0); + if (!dmixConfig) { + AFB_ReqFailF(request, "internal-error", "mixer=%s sink=%s fail to create DMIX config", mixer->uid, mixer->sinks[index]->uid); + goto OnErrorExit; + } + } + break; + default: + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s sinks invalid argsJ= %s", mixer->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + return 0; + +OnErrorExit: + return -1; +}
\ No newline at end of file diff --git a/plugins/alsa/alsa-api-source.c b/plugins/alsa/alsa-api-source.c new file mode 100644 index 0000000..6315a14 --- /dev/null +++ b/plugins/alsa/alsa-api-source.c @@ -0,0 +1,84 @@ +/* + * 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> + +PUBLIC AlsaSndCtlT *ApiSourceFindSubdev(SoftMixerT *mixer, const char *target) { + + // search for subdev into every registered source + for (int idx = 0; mixer->sources[idx]; idx++) { + if (mixer->sources[idx]->uid && !strcasecmp(mixer->sources[idx]->uid, target)) { + return mixer->sources[idx]->sndcard; + } + } + return NULL; +} + +PUBLIC int ApiSourceAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ) { + + int index; + for (index = 0; index < mixer->max.sources; index++) { + if (!mixer->sources[index]) break; + } + + if (index == mixer->max.sources) { + AFB_ReqFailF(request, "too-small", "mixer=%s max source=%d argsJ= %s", mixer->uid, mixer->max.sources, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + switch (json_object_get_type(argsJ)) { + long count; + + case json_type_object: + mixer->sources[index] = ApiPcmAttachOne(mixer, uid, SND_PCM_STREAM_CAPTURE, argsJ); + if (!mixer->sources[index]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s invalid source= %s", mixer->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + break; + + case json_type_array: + count = json_object_array_length(argsJ); + if (count > (mixer->max.sources - count)) { + AFB_ReqFailF(request, "too-small", "mixer=%s max source=%d argsJ= %s", mixer->uid, mixer->max.sources, json_object_get_string(argsJ)); + goto OnErrorExit; + + } + + for (int idx = 0; idx < count; idx++) { + json_object *sourceJ = json_object_array_get_idx(argsJ, idx); + mixer->sources[index + idx] = ApiPcmAttachOne(mixer, uid, SND_PCM_STREAM_CAPTURE, sourceJ); + if (!mixer->sources[index + idx]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s invalid source= %s", mixer->uid, json_object_get_string(sourceJ)); + goto OnErrorExit; + } + } + break; + default: + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s sources invalid argsJ= %s", mixer->uid, json_object_get_string(argsJ)); + goto OnErrorExit; + } + + return 0; + +OnErrorExit: + return -1; +}
\ No newline at end of file diff --git a/plugins/alsa/alsa-api-streams.c b/plugins/alsa/alsa-api-streams.c index 3923048..f531ed5 100644 --- a/plugins/alsa/alsa-api-streams.c +++ b/plugins/alsa/alsa-api-streams.c @@ -30,37 +30,22 @@ extern Lua2cWrapperT Lua2cWrap; typedef struct { - const char* verb; - AlsaLoopStreamT *stream; - SoftMixerHandleT *mixer; + AlsaStreamAudioT *stream; + SoftMixerT *mixer; AlsaVolRampT *ramp; + AlsaSndCtlT *sndcard; + snd_pcm_t *pcm; } apiHandleT; -STATIC AlsaVolRampT* RampGetByUid(CtlSourceT *source, AlsaVolRampT *ramps, const char *uid) { - AlsaVolRampT *ramp = NULL; - - // Loop on every Registryed zone pcm and extract (cardid) from (uid) - for (int idx = 0; ramps[idx].uid != NULL; idx++) { - if (!strcasecmp(ramps[idx].uid, uid)) { - ramp = &ramps[idx]; - return ramp; - } - } - return NULL; -} - STATIC void StreamApiVerbCB(AFB_ReqT request) { + apiHandleT *handle = (apiHandleT*) afb_request_get_vcbdata(request); int error, doClose = 0, doQuiet = 0, doToggle = 0, doMute = -1; long mute, volume; json_object *responseJ, *volumeJ = NULL, *rampJ = NULL, *argsJ = afb_request_json(request); - apiHandleT *handle = (apiHandleT*) afb_request_get_vcbdata(request); - snd_ctl_t *ctlDev = NULL; - CtlSourceT *source = alloca(sizeof (CtlSourceT)); - source->uid = handle->verb; - source->api = request->dynapi; - source->request = NULL; - source->context = NULL; + SoftMixerT *mixer = handle->mixer; + AlsaSndCtlT *sndcard = handle->sndcard; + assert(mixer && sndcard); error = wrap_json_unpack(argsJ, "{s?b s?b,s?b,s?b,s?o,s?o !}" , "quiet", &doQuiet @@ -71,31 +56,25 @@ STATIC void StreamApiVerbCB(AFB_ReqT request) { , "ramp", &rampJ ); if (error) { - AFB_ReqFailF(request, "StreamApiVerbCB", "Missing 'close|mute|volume|quiet' args=%s", json_object_get_string(argsJ)); - goto OnErrorExit; - } - - ctlDev = AlsaCtlOpenCtl(source, handle->mixer->frontend->cardid); - if (!ctlDev) { - AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to open sndcard=%s", handle->mixer->frontend->cardid); + AFB_ReqFailF(request, "syntax-error", "Missing 'close|mute|volume|quiet' args=%s", json_object_get_string(argsJ)); goto OnErrorExit; } if (doClose) { - AFB_ReqFailF(request, "StreamApiVerbCB", "(Fulup) Close action still to be done mixer=%s", json_object_get_string(argsJ)); + AFB_ReqFailF(request, "internal-error", "(Fulup) Close action still to be done mixer=%s stream=%s", mixer->uid, handle->stream->uid); goto OnErrorExit; } if (doToggle) { - error += AlsaCtlNumidGetLong(source, ctlDev, handle->stream->mute, &mute); - error += AlsaCtlNumidSetLong(source, ctlDev, handle->stream->mute, !mute); + error += AlsaCtlNumidGetLong(mixer, sndcard, handle->stream->mute, &mute); + error += AlsaCtlNumidSetLong(mixer, sndcard, handle->stream->mute, !mute); } if (volumeJ) { long curvol, newvol; const char*volString; - error = AlsaCtlNumidGetLong(source, ctlDev, handle->stream->volume, &curvol); + error = AlsaCtlNumidGetLong(mixer, handle->sndcard, handle->stream->volume, &curvol); if (error) { AFB_ReqFailF(request, "invalid-numid", "Fail to set volume numid=%d value=%ld", handle->stream->volume, volume); goto OnErrorExit; @@ -129,15 +108,15 @@ STATIC void StreamApiVerbCB(AFB_ReqT request) { } - error = AlsaCtlNumidSetLong(source, ctlDev, handle->stream->volume, newvol); + error = AlsaCtlNumidSetLong(mixer, handle->sndcard, handle->stream->volume, newvol); if (error) { AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volume numid=%d value=%ld", handle->stream->volume, newvol); goto OnErrorExit; } } - if (rampJ) { - error = AlsaVolRampApply(source, handle->mixer->frontend, handle->stream, handle->ramp, rampJ); + if (rampJ) { + error = AlsaVolRampApply(mixer, handle->sndcard, handle->stream, rampJ); if (error) { AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volram numid=%d value=%s", handle->stream->volume, json_object_get_string(rampJ)); goto OnErrorExit; @@ -145,7 +124,7 @@ STATIC void StreamApiVerbCB(AFB_ReqT request) { } if (doMute != -1) { - error = AlsaCtlNumidSetLong(source, ctlDev, handle->stream->mute, !mute); + error = AlsaCtlNumidSetLong(mixer, handle->sndcard, handle->stream->mute, !mute); if (error) { AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to set stream volume numid=%d value=%d", handle->stream->volume, !mute); goto OnErrorExit; @@ -155,8 +134,8 @@ STATIC void StreamApiVerbCB(AFB_ReqT request) { // if not in quiet mode return effective selected control values if (doQuiet) responseJ = NULL; else { - error += AlsaCtlNumidGetLong(source, ctlDev, handle->stream->volume, &volume); - error += AlsaCtlNumidGetLong(source, ctlDev, handle->stream->mute, &mute); + error += AlsaCtlNumidGetLong(mixer, handle->sndcard, handle->stream->volume, &volume); + error += AlsaCtlNumidGetLong(mixer, handle->sndcard, handle->stream->mute, &mute); if (error) { AFB_ReqFailF(request, "StreamApiVerbCB", "Fail to get stream numids volume=%ld mute=%ld", volume, mute); goto OnErrorExit; @@ -164,17 +143,188 @@ STATIC void StreamApiVerbCB(AFB_ReqT request) { wrap_json_pack(&responseJ, "{si,sb}", "volume", volume, "mute", !mute); } - snd_ctl_close(ctlDev); - AFB_ReqSucess(request, responseJ, handle->verb); + AFB_ReqSucess(request, responseJ, NULL); return; OnErrorExit: - if (ctlDev) snd_ctl_close(ctlDev); return; +} + +PUBLIC json_object *CreateOneStream(SoftMixerT *mixer, AlsaStreamAudioT *stream) { + int error; + long value; + AlsaSndLoopT *loop=NULL; + AlsaPcmCtlT *streamPcm; + AlsaSndCtlT *captureCard; + AlsaDevInfoT *captureDev = alloca(sizeof (AlsaDevInfoT)); + AlsaLoopSubdevT *loopDev; + char * captureName; + + loopDev = ApiLoopFindSubdev(mixer, stream->uid, stream->source, &loop); + if (loopDev) { + // create a valid PCM reference and try to open it. + captureDev->devpath = NULL; + captureDev->cardid = NULL; + captureDev->cardidx = loop->sndcard->cid.cardidx; + captureDev->device = loop->capture; + captureDev->subdev = loopDev->index; + captureCard = loop->sndcard; + } else { + // if capture UID is not present in loop search on sources + AlsaSndCtlT *sourceDev = ApiSourceFindSubdev(mixer, stream->source); + if (sourceDev) { + captureDev->devpath = NULL; + captureDev->cardid = NULL; + captureDev->cardidx = sourceDev->cid.cardidx; + captureDev->device = sourceDev->cid.device; + captureDev->subdev = sourceDev->cid.subdev; + captureCard = sourceDev; + } else { + AFB_ApiError(mixer->api, "CreateOneStream: mixer=%s stream=%s not found in loops/sources", mixer->uid, stream->uid); + goto OnErrorExit; + } + } + // check PCM is valid and get its full name + AlsaPcmCtlT *capturePcm = AlsaByPathOpenPcm(mixer, captureDev, SND_PCM_STREAM_CAPTURE); + if (!capturePcm) goto OnErrorExit; + + // Registry capturePcm PCM for active/pause event + if (loopDev && loopDev->numid) { + error = AlsaCtlRegister(mixer, captureCard, capturePcm, FONTEND_NUMID_RUN, loopDev->numid); + if (error) goto OnErrorExit; + } + + AlsaSndZoneT *zone = ApiZoneGetByUid(mixer, stream->sink); + if (!zone) { + AFB_ApiError(mixer->api, "CreateOneStream: mixer=%s stream=%s fail to find sink zone='%s'", mixer->uid, stream->uid, stream->sink); + goto OnErrorExit; + } + + // retrieve channel count from route and push it to stream + stream->params->channels = zone->ccount; + + // create mute control and Registry it as pause/resume ctl) + char *runName; + (void) asprintf(&runName, "pause-%s", stream->uid); + int pauseNumid = AlsaCtlCreateControl(mixer, captureCard, runName, 1, 0, 1, 1, stream->mute); + if (pauseNumid <= 0) goto OnErrorExit; + + // Registry stop/play as a pause/resume control + error = AlsaCtlRegister(mixer, captureCard, capturePcm, FONTEND_NUMID_PAUSE, pauseNumid); + if (error) goto OnErrorExit; + + + char *volName; + (void) asprintf(&volName, "vol-%s", stream->uid); + + // create stream and delay pcm opening until vol control is created + streamPcm = AlsaCreateSoftvol(mixer, stream, zone, captureCard, volName, VOL_CONTROL_MAX, 0); + if (!streamPcm) { + AFB_ApiError(mixer->api, "CreateOneStream: mixer=%s stream=%s fail to create stream", mixer->uid, stream->uid); + goto OnErrorExit; + } + + // create volume control before softvol pcm is opened + int volNumid = AlsaCtlCreateControl(mixer, captureCard, volName, stream->params->channels, VOL_CONTROL_MIN, VOL_CONTROL_MAX, VOL_CONTROL_STEP, stream->volume); + if (volNumid <= 0) goto OnErrorExit; + + if ((zone->params->rate != stream->params->rate) || (zone->params->format != stream->params->format)) { + char *rateName; + (void) asprintf(&rateName, "rate-%s", stream->uid); + streamPcm = AlsaCreateRate(mixer, rateName, streamPcm, zone->params, 0); + if (!streamPcm) { + AFB_ApiError(mixer->api, "StreamsAttach: mixer=%s stream=%s fail to create rate converter", mixer->uid, stream->uid); + goto OnErrorExit; + } + captureName=rateName; + } else { + captureName= (char*)streamPcm->cid.cardid; + } + + // everything is not ready to open playback pcm + error = snd_pcm_open(&streamPcm->handle, captureName, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (error) { + AFB_ApiError(mixer->api, "CreateOneStream: mixer=%s stream=%s fail to open capturePcm=%s error=%s", mixer->uid, stream->uid, streamPcm->cid.cardid, snd_strerror(error)); + goto OnErrorExit; + } + + // start stream pcm copy (at this both capturePcm & sink pcm should be open, we use output params to configure both in+outPCM) + error = AlsaPcmCopy(mixer, stream, capturePcm, streamPcm, stream->params); + if (error) goto OnErrorExit; + + error = AlsaCtlRegister(mixer, captureCard, capturePcm, FONTEND_NUMID_IGNORE, volNumid); + if (error) goto OnErrorExit; + + // when using loopdev check if subdev is active or not to prevent thread from reading empty packet + if (loopDev && loopDev->numid) { + // retrieve active/pause control and set PCM status accordingly + error = AlsaCtlNumidGetLong(mixer, captureCard, loopDev->numid, &value); + if (error) goto OnErrorExit; + + // toggle pause/resume (should be done after pcm_start) + if ((error = snd_pcm_pause(capturePcm->handle, !value)) < 0) { + AFB_ApiWarning(mixer->api, "CreateOneStream: mixer=%s [capturePcm=%s] fail to pause error=%s", mixer->uid, captureDev->cardid, snd_strerror(error)); + } + } + + // prepare response for application + json_object *paramsJ, *streamJ; + char *appCardId = NULL; + + // return alsa URI only when loopback is used + if (loop) { + (void) asprintf(&appCardId, "hw:%d,%d,%d", captureDev->cardidx, loop->playback, capturePcm->cid.subdev); + } else { + appCardId=""; + } + + error += wrap_json_pack(¶msJ, "{si si si si}" + , "rate", stream->params->rate + , "channels", stream->params->channels + , "format", stream->params->format + , "access", stream->params->access + ); + error += wrap_json_pack(&streamJ, "{ss ss si si so}", "uid" + , stream->uid, "alsa" + , appCardId + , "volid", volNumid + , "runid", pauseNumid + , "params", paramsJ + ); + if (error) { + AFB_ApiError(mixer->api, "CreateOneStream: mixer=%s stream=%s fail to prepare response", mixer->uid, stream->uid); + goto OnErrorExit; + } + + // create a dedicated verb for this stream + apiHandleT *apiHandle = calloc(1, sizeof (apiHandleT)); + + apiHandle->mixer = mixer; + apiHandle->stream = stream; + apiHandle->sndcard =captureCard; + apiHandle->pcm = capturePcm->handle; + + error = afb_dynapi_add_verb(mixer->api, stream->uid, stream->info, StreamApiVerbCB, apiHandle, NULL, 0); + if (error) { + AFB_ApiError(mixer->api, "CreateOneStream mixer=%s fail to Register API verb stream=%s", mixer->uid, stream->uid); + goto OnErrorExit; + } + + // Debug Alsa Config + //AlsaDumpElemConfig (source, "\n\nAlsa_Config\n------------\n", "pcm"); + //AlsaDumpPcmInfo(source, "\n\nPcm_config\n-----------\n", streamPcm->handle); + + AFB_ApiNotice(mixer->api, "CreateOneStream: mixer=%s stream=%s OK reponse=%s\n", mixer->uid, stream->uid, json_object_get_string(streamJ)); + + return streamJ; + +OnErrorExit: + return NULL; } -STATIC int ProcessOneStream(CtlSourceT *source, json_object *streamJ, AlsaLoopStreamT *stream) { +STATIC AlsaStreamAudioT * AttachOneStream(SoftMixerT *mixer, const char *uid, json_object *streamJ, json_object **responseJ) { + AlsaStreamAudioT *stream = calloc(1, sizeof (AlsaStreamAudioT)); int error; json_object *paramsJ = NULL; @@ -183,242 +333,93 @@ STATIC int ProcessOneStream(CtlSourceT *source, json_object *streamJ, AlsaLoopSt stream->mute = 0; stream->info = NULL; - error = wrap_json_unpack(streamJ, "{ss,s?s,ss,s?i,s?b,s?o,s?s !}" + error = wrap_json_unpack(streamJ, "{ss,s?s,ss,s?s,s?i,s?b,s?o,s?s !}" , "uid", &stream->uid , "info", &stream->info - , "zone", &stream->zone + , "zone", &stream->sink + , "source", &stream->source , "volume", &stream->volume , "mute", stream->mute , "params", ¶msJ , "ramp", &stream->ramp ); if (error) { - AFB_ApiNotice(source->api, "ProcessOneStream missing 'uid|[info]|zone|[volume]|[mute]|[params]' stream=%s", json_object_get_string(streamJ)); + AFB_ApiNotice(mixer->api, "ProcessOneStream hal=%s missing 'uid|[info]|zone|source||[volume]|[mute]|[params]' error=%s stream=%s", uid, wrap_json_get_error_string(error), json_object_get_string(streamJ)); goto OnErrorExit; } - if (paramsJ) error = ProcessSndParams(source, stream->uid, paramsJ, &stream->params); - if (error) { - AFB_ApiError(source->api, "ProcessOneSndCard: sndcard=%s invalid params=%s", stream->uid, json_object_get_string(paramsJ)); + stream->params = ApiPcmSetParams(mixer, stream->uid, paramsJ); + if (!stream->params) { + AFB_ApiError(mixer->api, "ProcessOneSndCard: hal=%s stream=%s invalid params=%s", uid, stream->uid, json_object_get_string(paramsJ)); goto OnErrorExit; - } else { - stream->params.rate = ALSA_DEFAULT_PCM_RATE; - stream->params.rate = ALSA_DEFAULT_PCM_RATE; - stream->params.access = SND_PCM_ACCESS_RW_INTERLEAVED; - stream->params.format = SND_PCM_FORMAT_S16_LE; - stream->params.channels = 2; - stream->params.sampleSize = 0; } // make sure remain valid even when json object is removed stream->uid = strdup(stream->uid); - stream->zone = strdup(stream->zone); + if (stream->sink)stream->sink = strdup(stream->sink); + if (stream->source)stream->source = strdup(stream->source); - return 0; + // implement stream PCM with corresponding thread and controls + *responseJ = CreateOneStream(mixer, stream); + + return stream; OnErrorExit: - return -1; + return NULL; } -PUBLIC int LoopStreams(CtlSourceT *source, json_object *argsJ, json_object **responseJ) { - SoftMixerHandleT *mixer = (SoftMixerHandleT*) source->context; - AlsaLoopStreamT *loopStream; - int error; - long value; - size_t count; +PUBLIC int ApiStreamAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ, json_object **responseJ) { + + if (!mixer->loops) { + AFB_ApiError(mixer->api, "StreamsAttach: mixer=%s No Loop found [should Registry snd_loop first]", mixer->uid); + goto OnErrorExit; + } - assert(mixer); + int index; + for (index = 0; index < mixer->max.streams; index++) { + if (!mixer->streams[index]) break; + } - // assert static/global softmixer handle get requited info - AlsaSndLoopT *ctlLoop = mixer->frontend; - if (!ctlLoop) { - AFB_ApiError(source->api, "LoopStreams: mixer=%s No Loop found [should Registry snd_loop first]", mixer->uid); + if (index == mixer->max.streams) { + AFB_ReqFailF(request, "too-small", "mixer=%s max stream=%d argsJ= %s", mixer->uid, mixer->max.streams, json_object_get_string(argsJ)); goto OnErrorExit; } switch (json_object_get_type(argsJ)) { + long count; + case json_type_object: - count = 1; - loopStream = calloc(count + 1, sizeof (AlsaLoopStreamT)); - error = ProcessOneStream(source, argsJ, &loopStream[0]); - if (error) { - AFB_ApiError(source->api, "LoopStreams: mixer=%s invalid stream= %s", mixer->uid, json_object_get_string(argsJ)); + mixer->streams[index] = AttachOneStream(mixer, uid, argsJ, responseJ); + if (!mixer->streams[index]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s invalid stream= %s", mixer->uid, json_object_get_string(argsJ)); goto OnErrorExit; } break; case json_type_array: + *responseJ = json_object_new_array(); + count = json_object_array_length(argsJ); - loopStream = calloc(count + 1, sizeof (AlsaLoopStreamT)); + if (count > (mixer->max.streams - count)) { + AFB_ReqFailF(request, "too-small", "mixer=%s max stream=%d argsJ= %s", mixer->uid, mixer->max.streams, json_object_get_string(argsJ)); + goto OnErrorExit; + } + for (int idx = 0; idx < count; idx++) { - json_object *loopStreamJ = json_object_array_get_idx(argsJ, idx); - error = ProcessOneStream(source, loopStreamJ, &loopStream[idx]); - if (error) { - AFB_ApiError(source->api, "loopstreams: mixer=%s invalid stream= %s", mixer->uid, json_object_get_string(loopStreamJ)); + json_object *streamJ = json_object_array_get_idx(argsJ, idx); + mixer->streams[index + idx] = AttachOneStream(mixer, uid, streamJ, &streamJ); + if (!mixer->streams[index + idx]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s invalid stream= %s", mixer->uid, json_object_get_string(streamJ)); goto OnErrorExit; } + (void) json_object_array_add(*responseJ, streamJ); } break; default: - AFB_ApiError(source->api, "LoopStreams: mixer=%s invalid argsJ= %s", mixer->uid, json_object_get_string(argsJ)); - goto OnErrorExit; - } - - - // return stream data to application as a json array - *responseJ = json_object_new_array(); - - for (int idx = 0; loopStream[idx].uid != NULL; idx++) { - json_object *streamJ, *paramsJ; - - // Search for a free loop capture device - AFB_ApiNotice(source->api, "LoopStreams: mixer=%s stream=%s Start", mixer->uid, (char*) loopStream[idx].uid); - ctlLoop->scount--; - if (ctlLoop->scount < 0) { - AFB_ApiError(source->api, "LoopStreams: mixer=%s stream=%s no more subdev avaliable in loopback=%s", mixer->uid, loopStream[idx].uid, ctlLoop->uid); - goto OnErrorExit; - } - - // Retrieve subdev loop device and open corresponding pcm - AlsaPcmInfoT *playbackDev = &ctlLoop->subdevs[ctlLoop->scount]; - - // capture use the same card/subdev as playback with a different device - playbackDev->device = ctlLoop->capture; - AlsaPcmInfoT *captureDev = AlsaByPathOpenPcm(source, playbackDev, SND_PCM_STREAM_CAPTURE); - if (!captureDev) goto OnErrorExit; - - // configure with default loopback subdev params - error = AlsaPcmConf(source, captureDev, &playbackDev->params); - if (error) goto OnErrorExit; - - // Registry capture PCM for active/pause event - if (captureDev->numid) { - error = AlsaCtlRegister(source, mixer, captureDev, FONTEND_NUMID_RUN, captureDev->numid); - if (error) goto OnErrorExit; - } - - // Try to create/setup volume control. - snd_ctl_t* ctlDev = AlsaCrlFromPcm(source, captureDev->handle); - if (!ctlDev) { - AFB_ApiError(source->api, "LoopStreams: mixer=%s [pcm=%s] fail attache sndcard", mixer->uid, captureDev->cardid); - goto OnErrorExit; - } - - // create mute control and Registry it as pause/resume ctl) - char runName[ALSA_CARDID_MAX_LEN]; - snprintf(runName, sizeof (runName), "run-%s", loopStream[idx].uid); - - // create a single boolean value control for pause/resume - int pauseNumid = AlsaCtlCreateControl(source, ctlDev, playbackDev, runName, 1, 0, 1, 1, loopStream[idx].mute); - if (pauseNumid <= 0) goto OnErrorExit; - - // Registry mute/unmute as a pause/resume control - error = AlsaCtlRegister(source, mixer, captureDev, FONTEND_NUMID_PAUSE, pauseNumid); - if (error) goto OnErrorExit; - - // create stream and delay pcm openning until vol control is created - char volName[ALSA_CARDID_MAX_LEN]; - snprintf(volName, sizeof (volName), "vol-%s", loopStream[idx].uid); - AlsaPcmInfoT *streamPcm = AlsaCreateSoftvol(source, &loopStream[idx], captureDev, volName, VOL_CONTROL_MAX, 0); - if (!streamPcm) { - AFB_ApiError(source->api, "LoopStreams: mixer=%s%s(pcm) fail to create stream", mixer->uid, loopStream[idx].uid); - goto OnErrorExit; - } - - // create volume control before softvol pcm is opened - int volNumid = AlsaCtlCreateControl(source, ctlDev, playbackDev, volName, streamPcm->params.channels, VOL_CONTROL_MIN, VOL_CONTROL_MAX, VOL_CONTROL_STEP, loopStream[idx].volume); - if (volNumid <= 0) goto OnErrorExit; - - // **** Fulup (would need some help to get automatic rate converter to work). - // // add a rate converter plugin to match stream params config - // char rateName[ALSA_CARDID_MAX_LEN]; - // snprintf(rateName, sizeof (rateName), "rate-%s", loopStream[idx].uid); - // AlsaPcmInfoT *ratePcm= AlsaCreateRate(source, rateName, streamPcm, 1); - // if (!ratePcm) { - // AFB_ApiError(source->api, "LoopStreams: mixer=%s%s(pcm) fail to create rate converter", loopStream[idx].uid); - // goto OnErrorExit; - // } - - // everything is not ready to open capture pcm - error = snd_pcm_open(&streamPcm->handle, loopStream[idx].uid, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); - if (error) { - AFB_ApiError(source->api, "LoopStreams: mixer=%s%s(pcm) fail to open capture", mixer->uid, loopStream[idx].uid); - goto OnErrorExit; - } - - // capture stream inherit channel from targeted zone - captureDev->ccount = streamPcm->ccount; - streamPcm->params.channels = streamPcm->ccount; - - // start stream pcm copy (at this both capture & sink pcm should be open, we use output params to configure both in+outPCM) - error = AlsaPcmCopy(source, &loopStream[idx], captureDev, streamPcm, &streamPcm->params); - if (error) goto OnErrorExit; - - error = AlsaCtlRegister(source, mixer, captureDev, FONTEND_NUMID_IGNORE, volNumid); - if (error) goto OnErrorExit; - - // retrieve active/pause control and set PCM status accordingly - error = AlsaCtlNumidGetLong(source, ctlDev, captureDev->numid, &value); - if (error) goto OnErrorExit; - - // toggle pause/resume (should be done after pcm_start) - if ((error = snd_pcm_pause(captureDev->handle, !value)) < 0) { - AFB_ApiWarning(source->api, "LoopStreams: mixer=%s [capture=%s] fail to pause error=%s", mixer->uid, captureDev->cardid, snd_strerror(error)); - } - - // prepare response for application - playbackDev->device = ctlLoop->playback; - error = AlsaByPathDevid(source, playbackDev); - - error += wrap_json_pack(¶msJ, "{si si si si}", "rate", streamPcm->params.rate, "channels", streamPcm->params.channels, "format", streamPcm->params.format, "access", streamPcm->params.access); - error += wrap_json_pack(&streamJ, "{ss ss si si so}", "uid", streamPcm->uid, "alsa", playbackDev->cardid, "volid", volNumid, "runid", pauseNumid, "params", paramsJ); - error += json_object_array_add(*responseJ, streamJ); - if (error) { - AFB_ApiError(source->api, "LoopStreams: mixer=%s stream=%s fail to prepare response", mixer->uid, captureDev->cardid); - goto OnErrorExit; - } - - // create a dedicated verb for this stream compose of mixeruid/streamuid - apiHandleT *apiHandle = calloc(1, sizeof (apiHandleT)); - char apiVerb[128]; - error = snprintf(apiVerb, sizeof (apiVerb), "%s/%s", mixer->uid, loopStream[idx].uid); - if (error == sizeof (apiVerb)) { - AFB_ApiError(source->api, "LoopStreams mixer=%s fail to Registry Stream API too long %s/%s", mixer->uid, mixer->uid, loopStream[idx].uid); - goto OnErrorExit; - } - - // if set get stream attached volramp - if (loopStream->ramp) { - apiHandle->ramp = RampGetByUid(source, ctlLoop->ramps, loopStream->ramp); - if (!apiHandle->ramp) { - AFB_ApiError(source->api, "LoopStreams: mixer=%s%s(pcm) fail to find ramp=%s", mixer->uid, loopStream[idx].uid, loopStream->ramp); - goto OnErrorExit; - } - } - - apiHandle->mixer = mixer; - apiHandle->stream= &loopStream[idx]; - apiHandle->verb = strdup(apiVerb); - error = afb_dynapi_add_verb(source->api, apiHandle->verb, loopStream[idx].info, StreamApiVerbCB, apiHandle, NULL, 0); - if (error) { - AFB_ApiError(source->api, "LoopStreams mixer=%s fail to Registry API verb=%s", mixer->uid, apiHandle->verb); + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s streams invalid argsJ= %s", mixer->uid, json_object_get_string(argsJ)); goto OnErrorExit; - } - - // free temporary resources - snd_ctl_close(ctlDev); - loopStream[idx].volume = volNumid; - loopStream[idx].mute = pauseNumid; - - // Debug Alsa Config - //AlsaDumpElemConfig (source, "\n\nAlsa_Config\n------------\n", "pcm"); - //AlsaDumpPcmInfo(source, "\n\nPcm_config\n-----------\n", streamPcm->handle); - - AFB_ApiNotice(source->api, "LoopStreams: mixer=%s stream=%s OK reponse=%s\n", mixer->uid, streamPcm->uid, json_object_get_string(streamJ)); } - // save handle for further use - mixer->streams = loopStream; return 0; OnErrorExit: diff --git a/plugins/alsa/alsa-api-zones.c b/plugins/alsa/alsa-api-zones.c index 8f383df..32af733 100644 --- a/plugins/alsa/alsa-api-zones.c +++ b/plugins/alsa/alsa-api-zones.c @@ -16,7 +16,7 @@ * */ -#define _GNU_SOURCE // needed for vasprintf +#define _GNU_MIXER // needed for vasprintf #include "alsa-softmixer.h" #include <string.h> @@ -24,133 +24,169 @@ // Fulup need to be cleanup with new controller version extern Lua2cWrapperT Lua2cWrap; -STATIC int ProcessOneChannel(CtlSourceT *source, const char* uid, json_object *channelJ, AlsaPcmChannelT *channel) { - const char*channelUid; +PUBLIC AlsaSndZoneT *ApiZoneGetByUid(SoftMixerT *mixer, const char *target) { - int error = wrap_json_unpack(channelJ, "{ss,si,s?i !}", "target", &channelUid, "channel", &channel->port); + assert(mixer->zones[0]); + + // search for subdev into every registered loop + for (int idx = 0; mixer->zones[idx]; idx++) { + if (mixer->zones[idx]->uid && !strcasecmp(mixer->zones[idx]->uid, target)) { + return mixer->zones[idx]; + } + } + AFB_ApiError(mixer->api, "ApiZoneGetByUid mixer=%s fail to find zone=%s", mixer->uid, target); + return NULL; +} + +STATIC AlsaPcmChannelT* ProcessOneChannel(SoftMixerT *mixer, const char* uid, json_object *channelJ) { + AlsaPcmChannelT *channel = calloc(1, sizeof (AlsaPcmChannelT)); + + int error = wrap_json_unpack(channelJ, "{ss,si !}" + , "target", &channel->uid + , "channel", &channel->port + ); if (error) goto OnErrorExit; - channel->uid = strdup(channelUid); - return 0; + channel->uid = strdup(channel->uid); + return channel; OnErrorExit: - AFB_ApiError(source->api, "ProcessOneChannel: zone=%s channel: missing (target||channel) json=%s", uid, json_object_get_string(channelJ)); - return -1; + AFB_ApiError(mixer->api, "ProcessOneChannel: zone=%s channel: missing (target|channel) json=%s", uid, json_object_get_string(channelJ)); + return NULL; } -STATIC int ProcessOneZone(CtlSourceT *source, json_object *zoneJ, AlsaSndZoneT *zone) { - json_object *mappingJ; +STATIC AlsaSndZoneT *AttacheOneZone(SoftMixerT *mixer, const char *uid, json_object *zoneJ) { + AlsaSndZoneT *zone = calloc(1, sizeof (AlsaSndZoneT)); + json_object *sinkJ = NULL, *sourceJ = NULL; size_t count; - const char* streamType; int error; - error = wrap_json_unpack(zoneJ, "{ss,s?s,so !}" + error = wrap_json_unpack(zoneJ, "{ss,s?o,s?o !}" , "uid", &zone->uid - , "type", &streamType - , "mapping", &mappingJ + , "sink", &sinkJ + , "source", &sourceJ ); - if (error) { - AFB_ApiNotice(source->api, "ProcessOneZone missing 'uid|type|mapping' zone=%s", json_object_get_string(zoneJ)); + if (error || (!sinkJ && sourceJ)) { + AFB_ApiNotice(mixer->api, "AttacheOneZone missing 'uid|sink|source' error=%s zone=%s", wrap_json_get_error_string(error), 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 (AlsaPcmChannelT)); - 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 (AlsaPcmChannelT)); - 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; + if (sinkJ) { + + switch (json_object_get_type(sinkJ)) { + case json_type_object: + zone->sinks = calloc(2, sizeof (void*)); + zone->sinks[0] = ProcessOneChannel(mixer, zone->uid, sinkJ); + if (!zone->sinks[0]) goto OnErrorExit; + + break; + case json_type_array: + count = json_object_array_length(sinkJ); + zone->sinks = calloc(count + 1, sizeof (void*)); + for (int idx = 0; idx < count; idx++) { + json_object *subdevJ = json_object_array_get_idx(sinkJ, idx); + zone->sinks[idx] = ProcessOneChannel(mixer, zone->uid, subdevJ); + if (error) goto OnErrorExit; + } + break; + default: + AFB_ApiError(mixer->api, "AttacheOneZone: Mixer=%s Hal=%s zone=%s invalid mapping=%s", mixer->uid, uid, zone->uid, json_object_get_string(sinkJ)); + goto OnErrorExit; + } + } - return 0; + if (sourceJ) { + switch (json_object_get_type(sourceJ)) { + case json_type_object: + zone->sources = calloc(2, sizeof (void*)); + zone->sources[0] = ProcessOneChannel(mixer, zone->uid, sourceJ); + if (!zone->sources[0]) goto OnErrorExit; + break; + case json_type_array: + count = json_object_array_length(sourceJ); + zone->sources = calloc(count + 1, sizeof (void*)); + for (int idx = 0; idx < count; idx++) { + json_object *subdevJ = json_object_array_get_idx(sourceJ, idx); + zone->sources[idx] = ProcessOneChannel(mixer, zone->uid, subdevJ); + if (error) goto OnErrorExit; + } + break; + default: + AFB_ApiError(mixer->api, "AttacheOneZone:Mixer=%s Hal=%s zone=%s mapping=%s", mixer->uid, uid, zone->uid, json_object_get_string(sourceJ)); + goto OnErrorExit; + } + } + + return zone; OnErrorExit: - return -1; + return NULL; } -PUBLIC int SndZones(CtlSourceT *source, json_object *argsJ) { - SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; - AlsaSndZoneT *zones=NULL; - int error; - size_t count; +PUBLIC int ApiZoneAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ) { - assert(mixerHandle); + int index; + for (index = 0; index < mixer->max.zones; index++) { + if (!mixer->zones[index]) break; + } - if (mixerHandle->routes) { - AFB_ApiError(source->api, "SndZones: mixer=%s Zones already Registryed %s", mixerHandle->uid, json_object_get_string(argsJ)); + if (index == mixer->max.zones) { + AFB_ReqFailF(request, "too-small", "mixer=%s max zone=%d argsJ= %s", mixer->uid, mixer->max.zones, json_object_get_string(argsJ)); goto OnErrorExit; } switch (json_object_get_type(argsJ)) { + long count; + case json_type_object: - count = 1; - zones = calloc(count + 1, sizeof (AlsaSndZoneT)); - error = ProcessOneZone(source, argsJ, &zones[0]); - if (error) { - AFB_ApiError(source->api, "SndZones: mixer=%s invalid zone= %s", mixerHandle->uid, json_object_get_string(argsJ)); + mixer->zones[index] = AttacheOneZone(mixer, uid, argsJ); + if (!mixer->zones[index]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s invalid zone= %s", mixer->uid, json_object_get_string(argsJ)); goto OnErrorExit; } + + AlsaPcmCtlT *routeConfig = AlsaCreateRoute(mixer, mixer->zones[index], 0); + if (!routeConfig) { + AFB_ApiError(mixer->api, "AttacheOneZone: Mixer=%s Hal=%s zone=%s Fail to attach PCM Route", mixer->uid, uid, mixer->zones[index]->uid); + goto OnErrorExit; + } + break; case json_type_array: count = json_object_array_length(argsJ); - zones = calloc(count + 1, sizeof (AlsaSndZoneT)); + if (count > (mixer->max.zones - count)) { + AFB_ReqFailF(request, "too-small", "mixer=%s max zone=%d argsJ= %s", mixer->uid, mixer->max.zones, json_object_get_string(argsJ)); + goto OnErrorExit; + + } + for (int idx = 0; idx < count; idx++) { - json_object *sndZoneJ = json_object_array_get_idx(argsJ, idx); - error = ProcessOneZone(source, sndZoneJ, &zones[idx]); - if (error) { - AFB_ApiError(source->api, "SndZones: mixer=%s invalid zone= %s", mixerHandle->uid, json_object_get_string(sndZoneJ)); + json_object *zoneJ = json_object_array_get_idx(argsJ, idx); + mixer->zones[index + idx] = AttacheOneZone(mixer, uid, zoneJ); + if (!mixer->zones[index + idx]) { + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s invalid zone= %s", mixer->uid, json_object_get_string(zoneJ)); + goto OnErrorExit; + } + + AlsaPcmCtlT *routeConfig = AlsaCreateRoute(mixer, mixer->zones[idx], 0); + if (!routeConfig) { + AFB_ApiError(mixer->api, "AttacheOneZone: Mixer=%s Hal=%s zone=%s Fail to attach PCM Route", mixer->uid, uid, mixer->zones[idx]->uid); goto OnErrorExit; } } break; default: - AFB_ApiError(source->api, "SndZones: mixer=%s invalid argsJ= %s", mixerHandle->uid, json_object_get_string(argsJ)); + AFB_ReqFailF(request, "invalid-syntax", "mixer=%s zones invalid argsJ= %s", mixer->uid, json_object_get_string(argsJ)); goto OnErrorExit; } - // Registry routed into global softmixer handle - mixerHandle->routes= calloc(count + 1, sizeof (AlsaPcmInfoT*)); - - // instantiate one route PCM per zone with multi plugin as slave - for (int idx = 0; zones[idx].uid != NULL; idx++) { - mixerHandle->routes[idx] = AlsaCreateRoute(source, &zones[idx], 0); - if (!mixerHandle->routes[idx]) { - AFB_ApiNotice(source->api, "SndZones: mixer=%s fail to create route zone=%s", mixerHandle->uid, zones[idx].uid); - goto OnErrorExit; - } - } - - free (zones); return 0; OnErrorExit: - if (zones) free(zones); 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 4ba871a..0ef11e1 100644 --- a/plugins/alsa/alsa-core-ctl.c +++ b/plugins/alsa/alsa-core-ctl.c @@ -29,18 +29,14 @@ for the specific language governing permissions and #include <sys/syscall.h> typedef struct { - AFB_ApiT api; + SoftMixerT *mixer; sd_event_source* evtsrc; - pthread_t thread; - int tid; - char* info; - snd_ctl_t *ctlDev; + const char* uid; + AlsaSndCtlT *sndcard; sd_event *sdLoop; - RegistryHandleT *registry; } SubscribeHandleT; - -PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNumidElemId(CtlSourceT *source, snd_ctl_t* ctlDev, int numid) { +PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNumidElemId(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numid) { char string[32]; int error; int index; @@ -49,19 +45,19 @@ PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNumidElemId(CtlSourceT *source, snd_ctl_t* c snd_ctl_elem_list_alloca(&ctlList); - if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { - AFB_ApiError(source->api, "AlsaCtlGetNumidElemId [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string)); + if ((error = snd_ctl_elem_list(sndcard->ctl, ctlList)) < 0) { + AFB_ApiError(mixer->api, "AlsaCtlGetNumidElemId [%s] fail retrieve controls", ALSA_CTL_UID(sndcard->ctl, string)); goto OnErrorExit; } if ((error = snd_ctl_elem_list_alloc_space(ctlList, snd_ctl_elem_list_get_count(ctlList))) < 0) { - AFB_ApiError(source->api, "AlsaCtlGetNumidElemId [%s] fail retrieve count", ALSA_CTL_UID(ctlDev, string)); + AFB_ApiError(mixer->api, "AlsaCtlGetNumidElemId [%s] fail retrieve count", ALSA_CTL_UID(sndcard->ctl, string)); goto OnErrorExit; } // Fulup: do not understand why snd_ctl_elem_list should be call twice to get a valid ctlCount - if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { - AFB_ApiError(source->api, "AlsaCtlGetNumidElemId [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string)); + if ((error = snd_ctl_elem_list(sndcard->ctl, ctlList)) < 0) { + AFB_ApiError(mixer->api, "AlsaCtlGetNumidElemId [%s] fail retrieve controls", ALSA_CTL_UID(sndcard->ctl, string)); goto OnErrorExit; } @@ -77,7 +73,7 @@ PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNumidElemId(CtlSourceT *source, snd_ctl_t* c } if (index == ctlCount) { - AFB_ApiNotice(source->api, "AlsaCtlGetNumidElemId [%s] fail get numid=%i count", ALSA_CTL_UID(ctlDev, string), numid); + AFB_ApiNotice(mixer->api, "AlsaCtlGetNumidElemId [%s] fail get numid=%i count", ALSA_CTL_UID(sndcard->ctl, string), numid); goto OnErrorExit; } @@ -90,8 +86,7 @@ OnErrorExit: return NULL; } -PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(CtlSourceT *source, snd_ctl_t* ctlDev, const char *ctlName) { - char string[32]; +PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName) { int error; int index; snd_ctl_elem_list_t *ctlList = NULL; @@ -99,19 +94,19 @@ PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(CtlSourceT *source, snd_ctl_t* ct snd_ctl_elem_list_alloca(&ctlList); - if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { - AFB_ApiError(source->api, "AlsaCtlGetNameElemId [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string)); + if ((error = snd_ctl_elem_list(sndcard->ctl, ctlList)) < 0) { + AFB_ApiError(mixer->api, "AlsaCtlGetNameElemId cardid='%s' cardname='%s' fail retrieve controls", sndcard->cid.cardid, sndcard->cid.name); goto OnErrorExit; } if ((error = snd_ctl_elem_list_alloc_space(ctlList, snd_ctl_elem_list_get_count(ctlList))) < 0) { - AFB_ApiError(source->api, "AlsaCtlGetNameElemId [%s] fail retrieve count", ALSA_CTL_UID(ctlDev, string)); + AFB_ApiError(mixer->api, "AlsaCtlGetNameElemId cardid='%s' cardname='%s' fail retrieve count", sndcard->cid.cardid, sndcard->cid.name); goto OnErrorExit; } // Fulup: do not understand why snd_ctl_elem_list should be call twice to get a valid ctlCount - if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) { - AFB_ApiError(source->api, "AlsaCtlGetNameElemId [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string)); + if ((error = snd_ctl_elem_list(sndcard->ctl, ctlList)) < 0) { + AFB_ApiError(mixer->api, "AlsaCtlGetNameElemId cardid='%s' cardname='%s' fail retrieve controls", sndcard->cid.cardid, sndcard->cid.name); goto OnErrorExit; } @@ -119,7 +114,7 @@ PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(CtlSourceT *source, snd_ctl_t* ct int ctlCount = snd_ctl_elem_list_get_used(ctlList); for (index = 0; index < ctlCount; index++) { - if (!strcasecmp(ctlName, snd_ctl_elem_list_get_name(ctlList, index))) { + if (strcasestr(snd_ctl_elem_list_get_name(ctlList, index), ctlName)) { snd_ctl_elem_id_malloc(&elemId); snd_ctl_elem_list_get_id(ctlList, index, elemId); break; @@ -127,7 +122,7 @@ PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(CtlSourceT *source, snd_ctl_t* ct } if (index == ctlCount) { - AFB_ApiNotice(source->api, "AlsaCtlGetNameElemId [%s] ctl not found name=%s", ALSA_CTL_UID(ctlDev, string), ctlName); + AFB_ApiNotice(mixer->api, "AlsaCtlGetNameElemId cardid='%s' cardname='%s' ctl not found name=%s", sndcard->cid.cardid, sndcard->cid.name, ctlName); goto OnErrorExit; } @@ -140,42 +135,26 @@ OnErrorExit: return NULL; } -PUBLIC snd_ctl_t *AlsaCtlOpenCtl(CtlSourceT *source, const char *cardid) { +PUBLIC snd_ctl_t *AlsaCtlOpenCtl(SoftMixerT *mixer, const char *cardid) { int error; - snd_ctl_t *ctlDev; + snd_ctl_t *ctl; if (!cardid) goto OnErrorExit; - if ((error = snd_ctl_open(&ctlDev, cardid, SND_CTL_READONLY)) < 0) { + if ((error = snd_ctl_open(&ctl, cardid, SND_CTL_READONLY)) < 0) { cardid = "Not Defined"; goto OnErrorExit; } - return ctlDev; + return ctl; OnErrorExit: - AFB_ApiError(source->api, "AlsaCtlOpenCtl: fail to find sndcard by id= %s", cardid); + AFB_ApiError(mixer->api, "AlsaCtlOpenCtl: fail to find sndcard by id= %s", cardid); return NULL; } -STATIC int CtlElemIdGetNumid(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, int *numid) { - snd_ctl_elem_info_t *elemInfo; - - snd_ctl_elem_info_alloca(&elemInfo); - snd_ctl_elem_info_set_id(elemInfo, elemId); - if (snd_ctl_elem_info(ctlDev, elemInfo) < 0) goto OnErrorExit; - if (!snd_ctl_elem_info_is_readable(elemInfo)) goto OnErrorExit; - - *numid = snd_ctl_elem_info_get_numid(elemInfo); - - return 0; - -OnErrorExit: - return -1; -} - -STATIC void CtlElemIdDisplay(AFB_ApiT api, snd_ctl_elem_info_t *elemInfo, snd_ctl_elem_value_t *elemData) { +STATIC void CtlElemIdDisplay(SoftMixerT *mixer, snd_ctl_elem_info_t *elemInfo, snd_ctl_elem_value_t *elemData) { int numid = snd_ctl_elem_info_get_numid(elemInfo); int count = snd_ctl_elem_info_get_count(elemInfo); @@ -184,7 +163,7 @@ STATIC void CtlElemIdDisplay(AFB_ApiT api, snd_ctl_elem_info_t *elemInfo, snd_ct if (!elemData) { - AFB_ApiWarning(api, "CtlElemIdDisplay: numid=%d name=%s value=unreadable", numid, name); + AFB_ApiWarning(mixer->api, "CtlElemIdDisplay: numid=%d name=%s value=unreadable", numid, name); } else for (int idx = 0; idx < count; idx++) { long valueL; @@ -192,67 +171,63 @@ STATIC void CtlElemIdDisplay(AFB_ApiT api, snd_ctl_elem_info_t *elemInfo, snd_ct switch (elemType) { case SND_CTL_ELEM_TYPE_BOOLEAN: valueL = snd_ctl_elem_value_get_boolean(elemData, idx); - AFB_ApiWarning(api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); + AFB_ApiWarning(mixer->api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); break; case SND_CTL_ELEM_TYPE_INTEGER: valueL = snd_ctl_elem_value_get_integer(elemData, idx); - AFB_ApiWarning(api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); + AFB_ApiWarning(mixer->api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); break; case SND_CTL_ELEM_TYPE_INTEGER64: valueL = snd_ctl_elem_value_get_integer64(elemData, idx); - AFB_ApiWarning(api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); + AFB_ApiWarning(mixer->api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); break; case SND_CTL_ELEM_TYPE_ENUMERATED: valueL = snd_ctl_elem_value_get_enumerated(elemData, idx); - AFB_ApiWarning(api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); + AFB_ApiWarning(mixer->api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); break; case SND_CTL_ELEM_TYPE_BYTES: valueL = snd_ctl_elem_value_get_byte(elemData, idx); - AFB_ApiWarning(api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); + AFB_ApiWarning(mixer->api, "CtlElemIdDisplay: numid=%d name=%s value=%ld", numid, name, valueL); break; case SND_CTL_ELEM_TYPE_IEC958: default: - AFB_ApiWarning(api, "CtlElemIdDisplay: numid=%d name=%s Unsupported type=%d", numid, name, elemType); + AFB_ApiWarning(mixer->api, "CtlElemIdDisplay: numid=%d name=%s Unsupported type=%d", numid, name, elemType); break; } } } -PUBLIC int CtlElemIdGetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, long *value) { +PUBLIC int CtlElemIdGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, snd_ctl_elem_id_t *elemId, long *value) { int error; snd_ctl_elem_value_t *elemData; snd_ctl_elem_info_t *elemInfo; snd_ctl_elem_info_alloca(&elemInfo); snd_ctl_elem_info_set_id(elemInfo, elemId); - if (snd_ctl_elem_info(ctlDev, elemInfo) < 0) goto OnErrorExit; + if (snd_ctl_elem_info(sndcard->ctl, elemInfo) < 0) goto OnErrorExit; if (!snd_ctl_elem_info_is_readable(elemInfo)) goto OnErrorExit; // as we have static rate/channel we should have only one boolean as value snd_ctl_elem_value_alloca(&elemData); snd_ctl_elem_value_set_id(elemData, elemId); - error = snd_ctl_elem_read(ctlDev, elemData); + error = snd_ctl_elem_read(sndcard->ctl, elemData); if (error) { elemData = NULL; goto OnErrorExit; } - // warning multi channel are always view as grouped - //int count = snd_ctl_elem_info_get_count(elemInfo); - //if (count != 1) goto OnErrorExit; - // value=1 when active and 0 when not active *value = (int) snd_ctl_elem_value_get_integer(elemData, 0); return 0; OnErrorExit: - CtlElemIdDisplay(api, elemInfo, elemData); + CtlElemIdDisplay(mixer, elemInfo, elemData); return -1; } -PUBLIC int CtlElemIdSetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, long value) { +PUBLIC int CtlElemIdSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, snd_ctl_elem_id_t *elemId, long value) { snd_ctl_elem_value_t *elemData; snd_ctl_elem_info_t *elemInfo; const char* name; @@ -260,7 +235,7 @@ PUBLIC int CtlElemIdSetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t * snd_ctl_elem_info_alloca(&elemInfo); snd_ctl_elem_info_set_id(elemInfo, elemId); - if (snd_ctl_elem_info(ctlDev, elemInfo) < 0) goto OnErrorExit; + if (snd_ctl_elem_info(sndcard->ctl, elemInfo) < 0) goto OnErrorExit; if (!snd_ctl_elem_info_is_writable(elemInfo)) goto OnErrorExit; @@ -269,15 +244,14 @@ PUBLIC int CtlElemIdSetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t * snd_ctl_elem_value_alloca(&elemData); snd_ctl_elem_value_set_id(elemData, elemId); - error = snd_ctl_elem_read(ctlDev, elemData); + error = snd_ctl_elem_read(sndcard->ctl, elemData); if (error) goto OnErrorExit; - for (int index = 0; index < count; index++) { snd_ctl_elem_value_set_integer(elemData, index, value); } - error = snd_ctl_elem_write(ctlDev, elemData); + error = snd_ctl_elem_write(sndcard->ctl, elemData); if (error) goto OnErrorExit; return 0; @@ -285,45 +259,43 @@ PUBLIC int CtlElemIdSetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t * OnErrorExit: numid = snd_ctl_elem_info_get_numid(elemInfo); name = snd_ctl_elem_info_get_name(elemInfo); - AFB_ApiError(api, "CtlElemIdSetInt: numid=%d name=%s not writable", numid, name); + AFB_ApiError(mixer->api, "CtlElemIdSetInt: numid=%d name=%s not writable", numid, name); return -1; } // Clone of AlsaLib snd_card_load2 static function -PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(CtlSourceT *source, const char *cardid) { +PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(SoftMixerT *mixer, const char *cardid) { int error; - snd_ctl_t *ctlDev; - - if (cardid) goto OnErrorExit; + snd_ctl_t *ctl; - if ((error = snd_ctl_open(&ctlDev, cardid, SND_CTL_READONLY)) < 0) { + if ((error = snd_ctl_open(&ctl, cardid, SND_CTL_READONLY)) < 0) { cardid = "Not Defined"; goto OnErrorExit; } snd_ctl_card_info_t *cardInfo = malloc(snd_ctl_card_info_sizeof()); - if ((error = snd_ctl_card_info(ctlDev, cardInfo)) < 0) { + if ((error = snd_ctl_card_info(ctl, cardInfo)) < 0) { goto OnErrorExit; } return cardInfo; OnErrorExit: - AFB_ApiError(source->api, "AlsaCtlGetInfo: fail to find sndcard by id= %s", cardid); + AFB_ApiError(mixer->api, "AlsaCtlGetInfo: fail to find sndcard by id= %s", cardid); return NULL; } -PUBLIC int AlsaCtlNumidSetLong(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long value) { +PUBLIC int AlsaCtlNumidSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numid, long value) { - snd_ctl_elem_id_t *elemId = AlsaCtlGetNumidElemId(source, ctlDev, numid); + snd_ctl_elem_id_t *elemId = AlsaCtlGetNumidElemId(mixer, sndcard, numid); if (!elemId) { - AFB_ApiError(source->api, "AlsaCtlNumidSetLong [sndcard=%s] fail to find numid=%d", snd_ctl_name(ctlDev), numid); + AFB_ApiError(mixer->api, "AlsaCtlNumidSetLong cardid=%s cardname=%s fail to find numid=%d", sndcard->cid.cardid, sndcard->cid.longname, numid); goto OnErrorExit; } - int error = CtlElemIdSetLong(source->api, ctlDev, elemId, value); + int error = CtlElemIdSetLong(mixer, sndcard, elemId, value); if (error) { - AFB_ApiError(source->api, "AlsaCtlNumidSetLong [sndcard=%s] fail to set numid=%d value=%ld", snd_ctl_name(ctlDev), numid, value); + AFB_ApiError(mixer->api, "AlsaCtlNumidSetLong cardid=%s cardname=%s fail to set numid=%d value=%ld", sndcard->cid.cardid, sndcard->cid.longname, numid, value); goto OnErrorExit; } @@ -332,17 +304,17 @@ OnErrorExit: return -1; } -PUBLIC int AlsaCtlNumidGetLong(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long* value) { +PUBLIC int AlsaCtlNumidGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numid, long* value) { - snd_ctl_elem_id_t *elemId = AlsaCtlGetNumidElemId(source, ctlDev, numid); + snd_ctl_elem_id_t *elemId = AlsaCtlGetNumidElemId(mixer, sndcard, numid); if (!elemId) { - AFB_ApiError(source->api, "AlsaCtlNumidGetLong [sndcard=%s] fail to find numid=%d", snd_ctl_name(ctlDev), numid); + AFB_ApiError(mixer->api, "AlsaCtlNumidGetLong cardid=%s cardname=%s fail to find numid=%d", sndcard->cid.cardid, sndcard->cid.longname, numid); goto OnErrorExit; } - int error = CtlElemIdGetLong(source->api, ctlDev, elemId, value); + int error = CtlElemIdGetLong(mixer, sndcard, elemId, value); if (error) { - AFB_ApiError(source->api, "AlsaCtlNumidGetLong [sndcard=%s] fail to get numid=%d value", snd_ctl_name(ctlDev), numid); + AFB_ApiError(mixer->api, "AlsaCtlNumidGetLong cardid=%s cardname=%s fail to get numid=%d value", sndcard->cid.cardid, sndcard->cid.longname, numid); goto OnErrorExit; } @@ -351,17 +323,17 @@ OnErrorExit: return -1; } -PUBLIC int AlsaCtlNameSetLong(CtlSourceT *source, snd_ctl_t* ctlDev, const char *ctlName, long value) { +PUBLIC int AlsaCtlNameSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName, long value) { - snd_ctl_elem_id_t *elemId = AlsaCtlGetNameElemId(source, ctlDev, ctlName); + snd_ctl_elem_id_t *elemId = AlsaCtlGetNameElemId(mixer, sndcard, ctlName); if (!elemId) { - AFB_ApiError(source->api, "AlsaCtlNameSetLong [sndcard=%s] fail to find crlName=%s", snd_ctl_name(ctlDev), ctlName); + AFB_ApiError(mixer->api, "AlsaCtlNameSetLong cardid=%s cardname=%s fail to find crlName=%s", sndcard->cid.cardid, sndcard->cid.longname, ctlName); goto OnErrorExit; } - int error = CtlElemIdSetLong(source->api, ctlDev, elemId, value); + int error = CtlElemIdSetLong(mixer, sndcard, elemId, value); if (error) { - AFB_ApiError(source->api, "AlsaCtlNameSetLong [sndcard=%s] fail to set crlName=%s value=%ld", snd_ctl_name(ctlDev), ctlName, value); + AFB_ApiError(mixer->api, "AlsaCtlNameSetLong cardid=%s cardname=%s fail to set crlName=%s value=%ld", sndcard->cid.cardid, sndcard->cid.longname, ctlName, value); goto OnErrorExit; } @@ -370,18 +342,17 @@ OnErrorExit: return -1; } +PUBLIC int AlsaCtlNameGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName, long* value) { -PUBLIC int AlsaCtlNameGetLong(CtlSourceT *source, snd_ctl_t* ctlDev, const char *ctlName, long* value) { - - snd_ctl_elem_id_t *elemId = AlsaCtlGetNameElemId(source, ctlDev, ctlName); + snd_ctl_elem_id_t *elemId = AlsaCtlGetNameElemId(mixer, sndcard, ctlName); if (!elemId) { - AFB_ApiError(source->api, "AlsaCtlNameGetLong [sndcard=%s] fail to find crlName=%s", snd_ctl_name(ctlDev), ctlName); + AFB_ApiError(mixer->api, "AlsaCtlNameGetLong cardid=%s cardname=%s fail to find crlName=%s", sndcard->cid.cardid, sndcard->cid.longname, ctlName); goto OnErrorExit; } - int error = CtlElemIdGetLong(source->api, ctlDev, elemId, value); + int error = CtlElemIdGetLong(mixer, sndcard, elemId, value); if (error) { - AFB_ApiError(source->api, "AlsaCtlNameGetLong [sndcard=%s] fail to get crlName=%s value", snd_ctl_name(ctlDev), ctlName); + AFB_ApiError(mixer->api, "AlsaCtlNameGetLong cardid=%s cardname=%s fail to get crlName=%s value", sndcard->cid.cardid, sndcard->cid.longname, ctlName); goto OnErrorExit; } @@ -390,7 +361,7 @@ OnErrorExit: return -1; } -STATIC int AlsaCtlMakeControl(CtlSourceT *source, snd_ctl_t* ctlDev, AlsaPcmInfoT *subdev, const char *ctlName, int ctlCount, int ctlMin, int ctlMax, int ctlStep) { +STATIC int AlsaCtlMakeControl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName, int ctlCount, int ctlMin, int ctlMax, int ctlStep) { snd_ctl_elem_type_t ctlType; snd_ctl_elem_info_t *elemInfo; int error; @@ -398,11 +369,11 @@ STATIC int AlsaCtlMakeControl(CtlSourceT *source, snd_ctl_t* ctlDev, AlsaPcmInfo snd_ctl_elem_info_alloca(&elemInfo); if (ctlName) snd_ctl_elem_info_set_name(elemInfo, ctlName); snd_ctl_elem_info_set_interface(elemInfo, SND_CTL_ELEM_IFACE_MIXER); - snd_ctl_elem_info(ctlDev, elemInfo); - + snd_ctl_elem_info(sndcard->ctl, elemInfo); + // map volume to sndcard device+subdev=0 - snd_ctl_elem_info_set_device(elemInfo, subdev->device); - snd_ctl_elem_info_set_subdevice(elemInfo, subdev->subdev); + snd_ctl_elem_info_set_device(elemInfo, sndcard->cid.device); + snd_ctl_elem_info_set_subdevice(elemInfo, sndcard->cid.subdev); snd_ctl_elem_info_set_device(elemInfo, 0); snd_ctl_elem_info_set_subdevice(elemInfo, 0); @@ -412,17 +383,17 @@ STATIC int AlsaCtlMakeControl(CtlSourceT *source, snd_ctl_t* ctlDev, AlsaPcmInfo switch (ctlType) { case SND_CTL_ELEM_TYPE_BOOLEAN: - error = snd_ctl_add_boolean_elem_set(ctlDev, elemInfo, 1, ctlCount); + error = snd_ctl_add_boolean_elem_set(sndcard->ctl, elemInfo, 1, ctlCount); if (error) goto OnErrorExit; break; case SND_CTL_ELEM_TYPE_INTEGER: - error = snd_ctl_add_integer_elem_set(ctlDev, elemInfo, 1, ctlCount, ctlMin, ctlMax, ctlStep); + error = snd_ctl_add_integer_elem_set(sndcard->ctl, elemInfo, 1, ctlCount, ctlMin, ctlMax, ctlStep); if (error) goto OnErrorExit; break; default: - AFB_ApiError(source->api, "AlsaCtlMakeControl:%s(subdev) fail to create %s(control)", subdev->uid, ctlName); + AFB_ApiError(mixer->api, "AlsaCtlMakeControl: mixer=%s cardid=%s cardname=%s fail to create %s(control)", mixer->uid, sndcard->cid.cardid, sndcard->cid.longname, ctlName); goto OnErrorExit; } @@ -434,46 +405,48 @@ OnErrorExit: return -1; } -PUBLIC int AlsaCtlCreateControl(CtlSourceT *source, snd_ctl_t* ctlDev, AlsaPcmInfoT *subdevs, char* ctlName, int ctlCount, int ctlMin, int ctlMax, int ctlStep, long value) { +PUBLIC int AlsaCtlCreateControl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, char* ctlName, int ctlCount, int ctlMin, int ctlMax, int ctlStep, long value) { int numid = -1; // if control does not exist then create - snd_ctl_elem_id_t *elemId = AlsaCtlGetNameElemId(source, ctlDev, ctlName); + snd_ctl_elem_id_t *elemId = AlsaCtlGetNameElemId(mixer, sndcard, ctlName); if (elemId) { numid = snd_ctl_elem_id_get_numid(elemId); } else { // create or get numid control when already exist - numid = AlsaCtlMakeControl(source, ctlDev, subdevs, ctlName, ctlCount, ctlMin, ctlMax, ctlStep); + numid = AlsaCtlMakeControl(mixer, sndcard, ctlName, ctlCount, ctlMin, ctlMax, ctlStep); if (numid <= 0) { - AFB_ApiError(source->api, "AlsaCtlCreateControl [sndcard=%s] fail to create ctlName=%s", snd_ctl_name(ctlDev), ctlName); + AFB_ApiError(mixer->api, "AlsaCtlCreateControl cardid=%s cardname=%s fail to create ctlName=%s", sndcard->cid.cardid, sndcard->cid.longname, ctlName); goto OnErrorExit; } - elemId = AlsaCtlGetNumidElemId(source, ctlDev, numid); + elemId = AlsaCtlGetNumidElemId(mixer, sndcard, numid); } - int error = CtlElemIdSetLong(source->api, ctlDev, elemId, value); + int error = CtlElemIdSetLong(mixer, sndcard, elemId, value); if (error) { - AFB_ApiError(source->api, "AlsaCtlCreateControl [sndcard=%s] fail to set ctlName=%s Numid=%d", snd_ctl_name(ctlDev), ctlName, numid); + AFB_ApiError(mixer->api, "AlsaCtlCreateControl cardid=%s cardname=%s fail to set ctlName=%s Numid=%d", sndcard->cid.cardid, sndcard->cid.longname, ctlName, numid); goto OnErrorExit; } - AFB_ApiNotice(source->api, "AlsaCtlCreateControl [sndcard=%s] ctl create name=%s numid=%d value=%ld", snd_ctl_name(ctlDev), ctlName, numid, value); + AFB_ApiNotice(mixer->api, "AlsaCtlCreateControl cardid=%s cardname=%s ctl create name=%s numid=%d value=%ld", sndcard->cid.cardid, sndcard->cid.longname, ctlName, numid, value); return numid; OnErrorExit: return -1; } STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData) { - int error, numid; - SubscribeHandleT *subscribeHandle = (SubscribeHandleT*) userData; + int error; + SubscribeHandleT *sHandle = (SubscribeHandleT*) userData; + AlsaSndCtlT *sndcard = sHandle->sndcard; + SoftMixerT *mixer = sHandle->mixer; snd_ctl_event_t *eventId; snd_ctl_elem_id_t *elemId; long value; - int idx; + int index; if ((revents & EPOLLHUP) != 0) { - AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB hanghup [card:%s disconnected]", subscribeHandle->info); + AFB_ApiNotice(mixer->api, "CtlSubscribeEventCB hanghup [card:%s disconnected]", sHandle->uid); goto OnSuccessExit; } @@ -483,7 +456,7 @@ STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, v snd_ctl_event_alloca(&eventId); snd_ctl_elem_id_alloca(&elemId); - error = snd_ctl_read(subscribeHandle->ctlDev, eventId); + error = snd_ctl_read(sndcard->ctl, eventId); if (error < 0) goto OnErrorExit; // we only process sndctrl element @@ -495,73 +468,52 @@ STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, v // extract element from event and get value snd_ctl_event_elem_get_id(eventId, elemId); - error = CtlElemIdGetLong(subscribeHandle->api, subscribeHandle->ctlDev, elemId, &value); + error = CtlElemIdGetLong(mixer, sHandle->sndcard, elemId, &value); if (error) goto OnErrorExit; + + // get numdid and name from elemId + snd_ctl_elem_info_t *elemInfo; + snd_ctl_elem_info_alloca(&elemInfo); + snd_ctl_elem_info_set_id(elemInfo, elemId); + if (snd_ctl_elem_info(sndcard->ctl, elemInfo) < 0) goto OnErrorExit; + int numid = snd_ctl_elem_info_get_numid(elemInfo); + const char *name= snd_ctl_elem_info_get_name(elemInfo); - error = CtlElemIdGetNumid(subscribeHandle->api, subscribeHandle->ctlDev, elemId, &numid); - if (error) goto OnErrorExit; + for (index = 0; sndcard->registry[index]; index++) { + if (sndcard->registry[index]->numid == numid) { - for (idx = 0; idx < subscribeHandle->registry->count; idx++) { - if (subscribeHandle->registry->stream[idx].numid == numid) { - const char *pcmName = subscribeHandle->registry->stream[idx].pcm->cardid; - - switch (subscribeHandle->registry->stream[idx].type) { - case FONTEND_NUMID_RUN: - snd_pcm_pause(subscribeHandle->registry->stream[idx].pcm->handle, (int)(!value)); - AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d pcm=%s numid=%d active=%ld", subscribeHandle->info, subscribeHandle->tid, pcmName, numid, value); + switch (sndcard->registry[index]->type) { + case FONTEND_NUMID_RUN: + (void) snd_pcm_pause(sndcard->registry[index]->pcm->handle, (int) (!value)); + AFB_ApiNotice(mixer->api, "CtlSubscribeEventCB:%s numid=%d name=%s active=%ld", sHandle->uid, numid, name, value); break; - case FONTEND_NUMID_PAUSE: - AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d pcm=%s numid=%d pause=%ld", subscribeHandle->info, subscribeHandle->tid, pcmName, numid, value); - snd_pcm_pause(subscribeHandle->registry->stream[idx].pcm->handle, (int)value); + case FONTEND_NUMID_PAUSE: + AFB_ApiNotice(mixer->api, "CtlSubscribeEventCB:%s numid=%d name=%s pause=%ld", sHandle->uid, numid, name, value); + (void) snd_pcm_pause(sndcard->registry[index]->pcm->handle, (int) value); break; case FONTEND_NUMID_IGNORE: - default: - AFB_ApiInfo(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d pcm=%s numid=%d ignored=%ld", subscribeHandle->info, subscribeHandle->tid, pcmName, numid, value); + default: + AFB_ApiInfo(mixer->api, "CtlSubscribeEventCB:%s numid=%d name=%s ignored=%ld", sHandle->uid, numid, name, value); } break; } } - if (idx == subscribeHandle->registry->count) { - char cardName[32]; - ALSA_CTL_UID(subscribeHandle->ctlDev, cardName); - AFB_ApiNotice(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d card=%s numid=%d (unknown)", subscribeHandle->info, subscribeHandle->tid, cardName, numid); + if (index == sndcard->rcount) { + AFB_ApiNotice(mixer->api, "CtlSubscribeEventCB:%s numid=%d (unknown)", sHandle->uid, numid); } OnSuccessExit: return 0; OnErrorExit: - AFB_ApiInfo(subscribeHandle->api, "CtlSubscribeEventCB: ignored unsupported event"); + AFB_ApiInfo(mixer->api, "CtlSubscribeEventCB: ignored unsupported event"); return 0; } -static void *LoopInThread(void *handle) { - SubscribeHandleT *subscribeHandle = (SubscribeHandleT*) handle; - int count = 0; - int watchdog = MAINLOOP_WATCHDOG * 1000; - subscribeHandle->tid = (int) syscall(SYS_gettid); - - AFB_ApiNotice(subscribeHandle->api, "LoopInThread:%s/%d Started", subscribeHandle->info, subscribeHandle->tid); - - /* loop until end */ - for (;;) { - int res = sd_event_run(subscribeHandle->sdLoop, watchdog); - if (res == 0) { - AFB_ApiInfo(subscribeHandle->api, "LoopInThread:%s/%d Idle count=%d", subscribeHandle->info, subscribeHandle->tid, count++); - continue; - } - if (res < 0) { - AFB_ApiError(subscribeHandle->api, "LoopInThread:%s/%d ERROR=%i Exit errno=%s", subscribeHandle->info, subscribeHandle->tid, res, strerror(res)); - break; - } - } - pthread_exit(0); -} - -PUBLIC snd_ctl_t* AlsaCrlFromPcm(CtlSourceT *source, snd_pcm_t *pcm) { +PUBLIC snd_ctl_t* AlsaCrlFromPcm(SoftMixerT *mixer, snd_pcm_t *pcm) { char buffer[32]; int error; - snd_ctl_t *ctlDev; + snd_ctl_t *ctl; snd_pcm_info_t *pcmInfo; snd_pcm_info_alloca(&pcmInfo); @@ -569,53 +521,40 @@ PUBLIC snd_ctl_t* AlsaCrlFromPcm(CtlSourceT *source, snd_pcm_t *pcm) { int pcmCard = snd_pcm_info_get_card(pcmInfo); snprintf(buffer, sizeof (buffer), "hw:%i", pcmCard); - if ((error = snd_ctl_open(&ctlDev, buffer, SND_CTL_READONLY)) < 0) goto OnErrorExit; + if ((error = snd_ctl_open(&ctl, buffer, SND_CTL_READONLY)) < 0) goto OnErrorExit; - return ctlDev; + return ctl; OnErrorExit: return NULL; } -PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t * ctlDev, RegistryHandleT *registry) { +PUBLIC int AlsaCtlSubscribe(SoftMixerT *mixer, const char *uid, AlsaSndCtlT *sndcard) { int error; char string [32]; struct pollfd pfds; - SubscribeHandleT *subscribeHandle = malloc(sizeof (SubscribeHandleT)); + SubscribeHandleT *handle = malloc(sizeof (SubscribeHandleT)); - subscribeHandle->api = source->api; - subscribeHandle->ctlDev = ctlDev; - subscribeHandle->info = "ctlEvt"; - subscribeHandle->registry= registry; + handle->mixer = mixer; + handle->sndcard = sndcard; + handle->uid = uid; // 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)); + if ((error = snd_ctl_subscribe_events(handle->sndcard->ctl, 1)) < 0) { + AFB_ApiError(mixer->api, "AlsaCtlSubscribe: fail sndcard=%s to subscribe events", ALSA_CTL_UID(handle->sndcard->ctl, string)); goto OnErrorExit; } // get pollfd attach to this sound board - int count = snd_ctl_poll_descriptors(ctlDev, &pfds, 1); + int count = snd_ctl_poll_descriptors(handle->sndcard->ctl, &pfds, 1); if (count != 1) { - AFB_ApiError(source->api, "AlsaCtlSubscribe: fail sndcard=%s get poll descriptors", ALSA_CTL_UID(ctlDev, string)); - goto OnErrorExit; - } - - // add poll descriptor to AGL systemd mainloop - if ((error = sd_event_new(&subscribeHandle->sdLoop)) < 0) { - fprintf(stderr, "AlsaCtlSubscribe: fail sndcard=%s creating a new loop", ALSA_CTL_UID(ctlDev, string)); + AFB_ApiError(mixer->api, "AlsaCtlSubscribe: fail sndcard=%s get poll descriptors", ALSA_CTL_UID(handle->sndcard->ctl, string)); goto OnErrorExit; } // Registry sound event to binder main loop - if ((error = sd_event_add_io(subscribeHandle->sdLoop, &subscribeHandle->evtsrc, pfds.fd, EPOLLIN, CtlSubscribeEventCB, subscribeHandle)) < 0) { - AFB_ApiError(source->api, "AlsaCtlSubscribe: Fail sndcard=%s adding mainloop", ALSA_CTL_UID(ctlDev, string)); - goto OnErrorExit; - } - - // start a thread with a mainloop to monitor Audio-Agent - if ((error = pthread_create(&subscribeHandle->thread, NULL, &LoopInThread, subscribeHandle)) < 0) { - AFB_ApiError(source->api, "AlsaCtlSubscribe: Fail sndcard=%s create waiting thread err=%d", ALSA_CTL_UID(ctlDev, string), error); + if ((error = sd_event_add_io(mixer->sdLoop, &handle->evtsrc, pfds.fd, EPOLLIN, CtlSubscribeEventCB, handle)) < 0) { + AFB_ApiError(mixer->api, "AlsaCtlSubscribe: Fail sndcard=%s adding mainloop", ALSA_CTL_UID(handle->sndcard->ctl, string)); goto OnErrorExit; } @@ -625,32 +564,29 @@ OnErrorExit: return -1; } -PUBLIC int AlsaCtlRegister(CtlSourceT *source, SoftMixerHandleT *mixer, AlsaPcmInfoT *pcm, RegistryNumidT type, int numid) { +PUBLIC int AlsaCtlRegister(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaPcmCtlT *pcmdev, RegistryNumidT type, int numid) { + int index; - RegistryHandleT *registry= mixer->frontend->registry; + for (index = 0; index < sndcard->rcount; index++) { + if (!sndcard->registry[index]) break; + } - int count = registry->count; - if (count > MAX_AUDIO_STREAMS) { - AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] to many audio stream max=%d", pcm->cardid, MAX_AUDIO_STREAMS); + if (index == sndcard->rcount) { + AFB_ApiError(mixer->api, "AlsaCtlRegister cardid=%s cardname=%s to many audio stream max=%ld", sndcard->cid.cardid, sndcard->cid.longname, sndcard->rcount); goto OnErrorExit; } - // If 1st registration then open a dev control channel to recieve events - if (!registry->ctlDev) { - snd_ctl_t* ctlDev = AlsaCrlFromPcm(source, pcm->handle); - if (!ctlDev) { - AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail attache sndcard", pcm->cardid); - goto OnErrorExit; - } - - AlsaCtlSubscribe(source, ctlDev, registry); + // If 1st registration then register to card event + if (index == 0) { + AlsaCtlSubscribe(mixer, sndcard->cid.cardid, sndcard); } // store PCM in order to pause/resume depending on event - registry->stream[count].pcm = pcm; - registry->stream[count].numid = numid; - registry->stream[count].type = type; - registry->count++; + RegistryEntryPcmT *entry = calloc(1, sizeof (RegistryEntryPcmT)); + sndcard->registry[index] = entry; + entry->pcm = pcmdev; + entry->numid = numid; + entry->type = type; return 0; diff --git a/plugins/alsa/alsa-core-pcm.c b/plugins/alsa/alsa-core-pcm.c index ad8e842..b69d8c6 100644 --- a/plugins/alsa/alsa-core-pcm.c +++ b/plugins/alsa/alsa-core-pcm.c @@ -60,27 +60,30 @@ STATIC int AlsaPeriodSize(snd_pcm_format_t pcmFormat) { return pcmSampleSize; } -PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *opts) { - char string[32]; +PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, AlsaPcmHwInfoT *opts) { int error; snd_pcm_hw_params_t *pxmHwParams; snd_pcm_sw_params_t *pxmSwParams; + snd_pcm_format_t format; + snd_pcm_access_t access; // retrieve hadware config from PCM snd_pcm_hw_params_alloca(&pxmHwParams); snd_pcm_hw_params_any(pcm->handle, pxmHwParams); if (!opts->access) opts->access = SND_PCM_ACCESS_RW_INTERLEAVED; + snd_pcm_hw_params_get_access(pxmHwParams, &access); error = snd_pcm_hw_params_set_access(pcm->handle, pxmHwParams, opts->access); if (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; + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Set_Interleave=%d Fail current=%d mode error=%s", mixer->uid, pcm->cid.cardid, opts->access, access, snd_strerror(error)); +//Fulup goto OnErrorExit; }; if (opts->format != SND_PCM_FORMAT_UNKNOWN) { + snd_pcm_hw_params_get_format(pxmHwParams, &format); if ((error = snd_pcm_hw_params_set_format(pcm->handle, pxmHwParams, opts->format)) < 0) { - 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); + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Set_Format=%d Fail current=%d error=%s", mixer->uid, pcm->cid.cardid, opts->format, format, snd_strerror(error)); + AlsaDumpFormats(mixer, pcm->handle); goto OnErrorExit; } } @@ -88,27 +91,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:%s Fail PCM=%s Set_Rate=%d error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), opts->rate, snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s FailSet_Rate=%d error=%s", mixer->uid, pcm->cid.cardid, opts->rate, snd_strerror(error)); goto OnErrorExit; } // check we got requested rate if (opts->rate != pcmRate) { - 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); + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Set_Rate Fail ask=%dHz get=%dHz", mixer->uid, pcm->cid.cardid,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:%s Fail PCM=%s Set_Channels=%d error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), opts->channels, snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Set_Channels=%d Fail error=%s",mixer->uid, pcm->cid.cardid, 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:%s Fail PCM=%s apply hwparams error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Fail apply hwparams error=%s", mixer->uid, pcm->cid.cardid, snd_strerror(error)); goto OnErrorExit; } @@ -118,7 +121,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:%s Fail PCM=%s unsupported format format=%d", pcm->uid, ALSA_PCM_UID(pcm->handle, string), opts->format); + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Fail unsupported format format=%d", mixer->uid, pcm->cid.cardid, opts->format); goto OnErrorExit; } @@ -127,17 +130,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:%s Fail to PCM=%s set_buffersize error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Fail set_buffersize error=%s", mixer->uid, pcm->cid.cardid, 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:%s Fail to push software=%s params error=%s", pcm->uid, ALSA_PCM_UID(pcm->handle, string), snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Fail to push params error=%s", mixer->uid, pcm->cid.cardid, snd_strerror(error)); goto OnErrorExit; }; - 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); + AFB_ApiNotice(mixer->api, "AlsaPcmConf: mixer=%s cardid=%s Done channels=%d rate=%d format=%d access=%d done", mixer->uid, pcm->cid.cardid, opts->channels, opts->rate, opts->format, opts->access); return 0; OnErrorExit: @@ -211,16 +214,19 @@ STATIC int AlsaPcmReadCB(sd_event_source* src, int fd, uint32_t revents, void* u } // effectively read pcmIn and push frame to pcmOut - framesIn = snd_pcm_readi(pcmCopyHandle->pcmIn, pcmCopyHandle->buffer, availIn); + framesIn = snd_pcm_readi(pcmCopyHandle->pcmIn, pcmCopyHandle->buffer, availOut); if (framesIn < 0 || framesIn != availIn) { AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PcmIn=%s UNDERUN frame=%ld", ALSA_PCM_UID(pcmCopyHandle->pcmIn, string), framesIn); + snd_pcm_prepare(pcmCopyHandle->pcmIn); goto ExitOnSuccess; } // In/Out frames transfer through buffer copy - framesOut = snd_pcm_writei(pcmCopyHandle->pcmOut, pcmCopyHandle->buffer, framesIn); + //framesOut = snd_pcm_writei(pcmCopyHandle->pcmOut, pcmCopyHandle->buffer, framesIn); + framesOut = snd_pcm_mmap_writei (pcmCopyHandle->pcmOut, pcmCopyHandle->buffer, framesIn); if (framesOut < 0 || framesOut != framesIn) { AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PcmOut=%s UNDERUN frame=%ld", ALSA_PCM_UID(pcmCopyHandle->pcmOut, string), (framesIn - framesOut)); + snd_pcm_prepare(pcmCopyHandle->pcmOut); goto ExitOnSuccess; } @@ -260,87 +266,92 @@ static void *LoopInThread(void *handle) { pthread_exit(0); } -PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaLoopStreamT *loopStream, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pcmOut, AlsaPcmHwInfoT * opts) { +PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT *pcmIn, AlsaPcmCtlT *pcmOut, AlsaPcmHwInfoT * opts) { char string[32]; struct pollfd *pcmInFds; int error; + + // Fulup need to check https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m___direct.html + + AlsaDumpPcmInfo(mixer,"PcmIn",pcmIn->handle); + AlsaDumpPcmInfo(mixer,"PcmOut",pcmOut->handle); + // prepare PCM for capture and replay + error = AlsaPcmConf(mixer, pcmIn, opts); + if (error) goto OnErrorExit; + // input and output should match - error = AlsaPcmConf(source, pcmOut, opts); + error = AlsaPcmConf(mixer, pcmOut, opts); if (error) goto OnErrorExit; // Prepare PCM for usage if ((error = snd_pcm_prepare(pcmOut->handle)) < 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail to prepare PCM=%s error=%s", ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmCopy: Fail to prepare PCM=%s error=%s", ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); goto OnErrorExit; }; - // prepare PCM for capture and replay - error = AlsaPcmConf(source, pcmIn, opts); - if (error) goto OnErrorExit; - // Prepare PCM for usage if ((error = snd_pcm_start(pcmIn->handle)) < 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail to prepare PCM=%s error=%s", ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmCopy: Fail to prepare PCM=%s error=%s", ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); goto OnErrorExit; }; - AlsaPcmCopyHandleT *pcmCopyHandle = &loopStream->copy; - pcmCopyHandle->info = "pcmCpy"; - pcmCopyHandle->pcmIn = pcmIn->handle; - pcmCopyHandle->pcmOut = pcmOut->handle; - pcmCopyHandle->api = source->api; - pcmCopyHandle->channels = opts->channels; - pcmCopyHandle->frameSize = opts->channels * opts->sampleSize; - pcmCopyHandle->frameCount = ALSA_BUFFER_FRAMES_COUNT; - pcmCopyHandle->buffer = malloc(pcmCopyHandle->frameCount * pcmCopyHandle->frameSize); + AlsaPcmCopyHandleT *cHandle= calloc(1, sizeof(AlsaPcmCopyHandleT)); + cHandle = cHandle; + cHandle->info = "pcmCpy"; + cHandle->pcmIn = pcmIn->handle; + cHandle->pcmOut = pcmOut->handle; + cHandle->api = mixer->api; + cHandle->channels = opts->channels; + cHandle->frameSize = opts->channels * opts->sampleSize; + cHandle->frameCount = ALSA_BUFFER_FRAMES_COUNT; + cHandle->buffer = malloc(cHandle->frameCount * cHandle->frameSize); - // get FD poll descriptor for capture PCM - int pcmInCount = snd_pcm_poll_descriptors_count(pcmCopyHandle->pcmIn); + int pcmInCount = snd_pcm_poll_descriptors_count(cHandle->pcmIn); if (pcmInCount <= 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail pcmIn=%s get fds count error=%s", ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmCopy: Fail pcmIn=%s get fds count error=%s", ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error)); goto OnErrorExit; }; pcmInFds = alloca(sizeof (*pcmInFds) * pcmInCount); if ((error = snd_pcm_poll_descriptors(pcmIn->handle, pcmInFds, pcmInCount)) < 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail pcmIn=%s get pollfds error=%s", ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaPcmCopy: Fail pcmIn=%s get pollfds error=%s", ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error)); goto OnErrorExit; }; // add poll descriptor to AGL systemd mainloop - if ((error = sd_event_new(&pcmCopyHandle->sdLoop)) < 0) { + if ((error = sd_event_new(&cHandle->sdLoop)) < 0) { fprintf(stderr, "LaunchCallRequest: fail pcmin=%s creating a new loop: %s\n", ALSA_PCM_UID(pcmOut->handle, string), strerror(error)); goto OnErrorExit; } for (int idx = 0; idx < pcmInCount; idx++) { - if ((error = sd_event_add_io(pcmCopyHandle->sdLoop, &pcmCopyHandle->evtsrc, pcmInFds[idx].fd, EPOLLIN, AlsaPcmReadCB, pcmCopyHandle)) < 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail pcmIn=%s sd_event_add_io err=%d", ALSA_PCM_UID(pcmIn->handle, string), error); + if ((error = sd_event_add_io(cHandle->sdLoop, &cHandle->evtsrc, pcmInFds[idx].fd, EPOLLIN, AlsaPcmReadCB, cHandle)) < 0) { + AFB_ApiError(mixer->api, "AlsaPcmCopy: Fail pcmIn=%s sd_event_add_io err=%d", ALSA_PCM_UID(pcmIn->handle, string), error); goto OnErrorExit; } } // start a thread with a mainloop to monitor Audio-Agent - if ((error = pthread_create(&pcmCopyHandle->thread, NULL, &LoopInThread, pcmCopyHandle)) < 0) { - AFB_ApiError(source->api, "AlsaPcmCopy: Fail create waiting thread pcmIn=%s err=%d", ALSA_PCM_UID(pcmIn->handle, string), error); + if ((error = pthread_create(&cHandle->thread, NULL, &LoopInThread, cHandle)) < 0) { + AFB_ApiError(mixer->api, "AlsaPcmCopy: Fail create waiting thread pcmIn=%s err=%d", ALSA_PCM_UID(pcmIn->handle, string), error); goto OnErrorExit; } // request a higher priority for each audio stream thread struct sched_param params; params.sched_priority = sched_get_priority_max(SCHED_FIFO); - error= pthread_setschedparam(pcmCopyHandle->thread, SCHED_FIFO, ¶ms); + error= pthread_setschedparam(cHandle->thread, SCHED_FIFO, ¶ms); if (error) { - AFB_ApiWarning(source->api, "AlsaPcmCopy: Fail create increase stream thread priority pcmIn=%s err=%s", ALSA_PCM_UID(pcmIn->handle, string), strerror(error)); + AFB_ApiWarning(mixer->api, "AlsaPcmCopy: Fail create increase stream thread priority pcmIn=%s err=%s", ALSA_PCM_UID(pcmIn->handle, string), strerror(error)); } return 0; OnErrorExit: - AFB_ApiError(source->api, "AlsaPcmCopy: - pcmIn=%s" , ALSA_PCM_UID(pcmIn->handle, string)); - AFB_ApiError(source->api, "AlsaPcmCopy: - pcmOut=%s", ALSA_PCM_UID(pcmOut->handle, string)); + AFB_ApiError(mixer->api, "AlsaPcmCopy: - pcmIn=%s" , ALSA_PCM_UID(pcmIn->handle, string)); + AFB_ApiError(mixer->api, "AlsaPcmCopy: - pcmOut=%s", ALSA_PCM_UID(pcmOut->handle, string)); return -1; } diff --git a/plugins/alsa/alsa-effect-ramp.c b/plugins/alsa/alsa-effect-ramp.c index c861d0e..86bdd24 100644 --- a/plugins/alsa/alsa-effect-ramp.c +++ b/plugins/alsa/alsa-effect-ramp.c @@ -24,118 +24,152 @@ for the specific language governing permissions and #define _GNU_SOURCE // needed for vasprintf -#include "alsa-softmixer.h" #include <pthread.h> #include <sys/syscall.h> +#include "alsa-softmixer.h" + typedef struct { const char *uid; - long current; - long target; + long current; + long target; int numid; - snd_ctl_t *ctlDev; - AlsaVolRampT *params; - sd_event_source *evtsrc; - CtlSourceT *source; + AlsaSndCtlT *sndcard; + AlsaVolRampT *ramp; + sd_event_source *evtsrc; + SoftMixerT *mixer; sd_event *sdLoop; } VolRampHandleT; STATIC int VolRampTimerCB(sd_event_source* source, uint64_t timer, void* handle) { - VolRampHandleT *rampHandle = (VolRampHandleT*) handle; + VolRampHandleT *rHandle = (VolRampHandleT*)handle; int error; uint64_t usec; // RampDown - if (rampHandle->current > rampHandle->target) { - rampHandle->current = rampHandle->current - rampHandle->params->stepDown; - if (rampHandle->current < rampHandle->target) rampHandle->current = rampHandle->target; + if (rHandle->current > rHandle->target) { + rHandle->current = rHandle->current - rHandle->ramp->stepDown; + if (rHandle->current < rHandle->target) rHandle->current = rHandle->target; } // RampUp - if (rampHandle->current < rampHandle->target) { - rampHandle->current = rampHandle->current + rampHandle->params->stepUp; - if (rampHandle->current > rampHandle->target) rampHandle->current = rampHandle->target; + if (rHandle->current < rHandle->target) { + rHandle->current = rHandle->current + rHandle->ramp->stepUp; + if (rHandle->current > rHandle->target) rHandle->current = rHandle->target; } - - error = AlsaCtlNumidSetLong(rampHandle->source, rampHandle->ctlDev, rampHandle->numid, rampHandle->current); + + error = AlsaCtlNumidSetLong(rHandle->mixer, rHandle->sndcard, rHandle->numid, rHandle->current); if (error) goto OnErrorExit; // we reach target stop volram event - if (rampHandle->current == rampHandle->target) { - sd_event_source_unref(rampHandle->evtsrc); - snd_ctl_close (rampHandle->ctlDev); - free (rampHandle); - } - else { + if (rHandle->current == rHandle->target) { + sd_event_source_unref(rHandle->evtsrc); + free(rHandle); + } else { // otherwise validate timer for a new run - sd_event_now(rampHandle->sdLoop, CLOCK_MONOTONIC, &usec); - sd_event_source_set_enabled(rampHandle->evtsrc, SD_EVENT_ONESHOT); - error = sd_event_source_set_time(rampHandle->evtsrc, usec + rampHandle->params->delay); + sd_event_now(rHandle->sdLoop, CLOCK_MONOTONIC, &usec); + sd_event_source_set_enabled(rHandle->evtsrc, SD_EVENT_ONESHOT); + error = sd_event_source_set_time(rHandle->evtsrc, usec + rHandle->ramp->delay); } return 0; OnErrorExit: - AFB_ApiWarning(rampHandle->source->api, "VolRampTimerCB stream=%s numid=%d value=%ld", rampHandle->uid, rampHandle->numid, rampHandle->current); + AFB_ApiWarning(rHandle->mixer->api, "VolRampTimerCB stream=%s numid=%d value=%ld", rHandle->uid, rHandle->numid, rHandle->current); sd_event_source_unref(source); // abandon volRamp return -1; } -PUBLIC int AlsaVolRampApply(CtlSourceT *source, AlsaSndLoopT *frontend, AlsaLoopStreamT *stream, AlsaVolRampT *ramp, json_object *volumeJ) { +PUBLIC int AlsaVolRampApply(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaStreamAudioT *stream, json_object *rampJ) { long curvol, newvol; - const char*volString; - int error; + const char *uid, *volS; + json_object *volJ; + int error, index; uint64_t usec; - - snd_ctl_t *ctlDev =AlsaCtlOpenCtl(source, frontend->cardid); - if (!ctlDev) goto OnErrorExit; - - error = AlsaCtlNumidGetLong(source, ctlDev, stream->volume, &curvol); + + error = wrap_json_unpack(rampJ, "{ss so !}" + , "uid", &uid + , "vol", &volJ + ); if (error) { - AFB_ApiError(source->api,"AlsaVolRampApply:%s(stream) Fail to get volume numid=%d", stream->uid, stream->volume); + AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s invalid-json should {uid:ramp, vol:[+,-,=]value} ramp=%s", mixer->uid, stream->uid, json_object_get_string(rampJ)); goto OnErrorExit; } - switch (json_object_get_type(volumeJ)) { + switch (json_object_get_type(volJ)) { + int count; + case json_type_string: - volString = json_object_get_string(volumeJ); - switch (volString[0]) { + volS = json_object_get_string(volJ); + + switch (volS[0]) { case '+': - sscanf(&volString[1], "%ld", &newvol); + count= sscanf(&volS[1], "%ld", &newvol); newvol = curvol + newvol; break; case '-': - sscanf(&volString[1], "%ld", &newvol); + count= sscanf(&volS[1], "%ld", &newvol); newvol = curvol - newvol; break; + + case '=': + count= sscanf(&volS[1], "%ld", &newvol); + break; + default: - AFB_ApiError(source->api,"AlsaVolRampApply:%s(stream) relative volume should start by '+|-' value=%s", stream->uid, json_object_get_string(volumeJ)); - goto OnErrorExit; + // hope for int as a string and force it as relative + sscanf(&volS[0], "%ld", &newvol); + if (newvol < 0) newvol = curvol - newvol; + else newvol = curvol + newvol; + } + + if (count != 1) { + AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s invalid-numeric expect {uid:%s, vol:[+,-,=]value} get vol:%s", mixer->uid, stream->uid, uid, json_object_get_string(volJ)); + goto OnErrorExit; } break; case json_type_int: - newvol = json_object_get_int(volumeJ); + newvol = json_object_get_int(volJ); break; + default: - AFB_ApiError(source->api,"AlsaVolRampApply:%s(stream) volume should be string or integer value=%s", stream->uid, json_object_get_string(volumeJ)); + AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s invalid-type expect {uid:%s, vol:[+,-,=]value} get vol:%s", mixer->uid, stream->uid, uid, json_object_get_string(volJ)); goto OnErrorExit; } - VolRampHandleT *rampHandle = calloc(1, sizeof (VolRampHandleT)); - rampHandle->uid= stream->uid; - rampHandle->numid= stream->volume; - rampHandle->ctlDev= ctlDev; - rampHandle->source = source; - rampHandle->params = ramp; - rampHandle->target = newvol; - rampHandle->current= curvol; - rampHandle->sdLoop= stream->copy.sdLoop; + error = AlsaCtlNumidGetLong(mixer, sndcard, stream->volume, &curvol); + if (error) { + AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s ramp=%s Fail to get volume from numid=%d", mixer->uid, stream->uid, uid, stream->volume); + goto OnErrorExit; + } + // search for ramp uid in mixer + for (index=0; index<= mixer->max.ramps; index++) { + if (!strcasecmp(mixer->ramps[index]->uid, uid)) { + break; + } + } + + if (index == mixer->max.ramps) { + AFB_ApiError(mixer->api, "AlsaVolRampApply:mixer=%s stream=%s ramp=%s does not exit", mixer->uid, stream->uid, uid); + goto OnErrorExit; + } + + VolRampHandleT *rHandle = calloc(1, sizeof (VolRampHandleT)); + rHandle->uid = stream->uid; + rHandle->numid = stream->volume; + rHandle->sndcard = sndcard; + rHandle->mixer = mixer; + rHandle->ramp = mixer->ramps[index]; + rHandle->target = newvol; + rHandle->current = curvol; + rHandle->sdLoop = mixer->sdLoop; + // set a timer with ~250us accuracy - sd_event_now(rampHandle->sdLoop, CLOCK_MONOTONIC, &usec); - (void)sd_event_add_time(rampHandle->sdLoop, &rampHandle->evtsrc, CLOCK_MONOTONIC, usec+100, 250, VolRampTimerCB, rampHandle); + sd_event_now(rHandle->sdLoop, CLOCK_MONOTONIC, &usec); + (void) sd_event_add_time(rHandle->sdLoop, &rHandle->evtsrc, CLOCK_MONOTONIC, usec + 100, 250, VolRampTimerCB, rHandle); return 0; diff --git a/plugins/alsa/alsa-plug-dmix.c b/plugins/alsa/alsa-plug-dmix.c index b06d5f6..a9f6b7e 100644 --- a/plugins/alsa/alsa-plug-dmix.c +++ b/plugins/alsa/alsa-plug-dmix.c @@ -25,19 +25,16 @@ static int uniqueIpcIndex = 1024; ALSA_PLUG_PROTO(dmix); -PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave, int open) { +PUBLIC AlsaPcmCtlT* AlsaCreateDmix(SoftMixerT *mixer, const char* pcmName, AlsaSndPcmT *pcmSlave, int open) { snd_config_t *dmixConfig, *slaveConfig, *elemConfig, *pcmConfig; - AlsaPcmInfoT *pcmPlug= calloc(1,sizeof(AlsaPcmInfoT)); - pcmPlug->uid= strdup(pcmName); - pcmPlug->cardid=pcmPlug->uid; - + AlsaPcmCtlT *pcmPlug= calloc(1,sizeof(AlsaPcmCtlT)); + AlsaSndCtlT *sndSlave=pcmSlave->sndcard; + pcmPlug->cid.cardid=pcmName; 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->cardid); + error += snd_config_set_id (dmixConfig, pcmPlug->cid.cardid); error += snd_config_imake_string(&elemConfig, "type", "dmix"); error += snd_config_add(dmixConfig, elemConfig); error += snd_config_imake_integer(&elemConfig, "ipc_key", uniqueIpcIndex++); @@ -45,10 +42,10 @@ 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->cardid); - if (pcmSlave->params.rate) { + error += snd_config_imake_string(&elemConfig, "pcm", sndSlave->cid.cardid); + if (sndSlave->params->rate) { error += snd_config_add(slaveConfig, elemConfig); - error += snd_config_imake_integer(&elemConfig, "rate", pcmSlave->params.rate); + error += snd_config_imake_integer(&elemConfig, "rate", sndSlave->params->rate); } error += snd_config_add(slaveConfig, elemConfig); if (error) goto OnErrorExit; @@ -57,26 +54,28 @@ PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, Als error += snd_config_add(dmixConfig, slaveConfig); if (error) goto OnErrorExit; - if (open) error = _snd_pcm_dmix_open(&pcmPlug->handle, pcmPlug->cardid, snd_config, dmixConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); + if (open) error = _snd_pcm_dmix_open(&pcmPlug->handle, pcmPlug->cid.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 Error=%s", pcmPlug->cardid, pcmSlave->cardid, snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaCreateDmix: fail to create Dmix=%s Slave=%s Error=%s", sndSlave->cid.cardid, sndSlave->cid.cardid, snd_strerror(error)); goto OnErrorExit; } + snd_config_update(); 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->cardid); + AFB_ApiError(mixer->api, "AlsaCreateDmix: fail to add configDMIX=%s", pcmPlug->cid.cardid); goto OnErrorExit; } // Debug config & pcm - //AlsaDumpCtlConfig (source, "plug-dmix", dmixConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateDmix: %s done\n", pcmPlug->cardid); + //AlsaDumpCtlConfig (mixer, "plug-dmix", dmixConfig, 1); + AFB_ApiNotice(mixer->api, "AlsaCreateDmix: %s done\n", pcmPlug->cid.cardid); return pcmPlug; OnErrorExit: - AlsaDumpCtlConfig(source, "plug-dmix", dmixConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateDmix: OnErrorExit\n"); + AlsaDumpCtlConfig(mixer, "plug-pcm", pcmConfig, 1); + AlsaDumpCtlConfig(mixer, "plug-dmix", dmixConfig, 1); + AFB_ApiNotice(mixer->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 deleted file mode 100644 index c28508c..0000000 --- a/plugins/alsa/alsa-plug-multi.c +++ /dev/null @@ -1,106 +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(multi); - -PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmUid, int open) { - SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; - 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; - - assert(mixerHandle); - - AlsaPcmInfoT* pcmSlaves=mixerHandle->backend; - if (!pcmSlaves) { - AFB_ApiError(source->api, "AlsaCreateMulti: No Sound Card find [should Registry 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]; - AlsaPcmChannelT *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->uid); - 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(); - - if (open) 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); - 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-rate.c b/plugins/alsa/alsa-plug-rate.c index a9b9e23..4c83c7f 100644 --- a/plugins/alsa/alsa-plug-rate.c +++ b/plugins/alsa/alsa-plug-rate.c @@ -23,59 +23,60 @@ ALSA_PLUG_PROTO(rate); +PUBLIC AlsaPcmCtlT* AlsaCreateRate(SoftMixerT *mixer, const char* pcmName, AlsaPcmCtlT *pcmSlave, AlsaPcmHwInfoT *params, int open) { -PUBLIC AlsaPcmInfoT* AlsaCreateRate(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave, int open) { - snd_config_t *rateConfig, *slaveConfig, *elemConfig, *pcmConfig; - AlsaPcmInfoT *pcmPlug= calloc(1,sizeof(AlsaPcmInfoT)); - pcmPlug->uid= strdup(pcmName); - pcmPlug->cardid=pcmPlug->uid; + AlsaPcmCtlT *pcmPlug = calloc(1, sizeof (AlsaPcmCtlT)); + pcmPlug->cid.cardid = pcmName; - int error=0; + int error = 0; // refresh global alsalib config and create PCM top config snd_config_update(); error += snd_config_top(&rateConfig); - error += snd_config_set_id (rateConfig, pcmPlug->cardid); - error += snd_config_imake_string(&elemConfig, "type", "plug"); + error += snd_config_set_id(rateConfig, pcmPlug->cid.cardid); + error += snd_config_imake_string(&elemConfig, "type", "rate"); error += snd_config_add(rateConfig, elemConfig); if (error) goto OnErrorExit; error += snd_config_make_compound(&slaveConfig, "slave", 0); - error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->cardid); - if (pcmSlave->params.rate) { + error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->cid.cardid); + error += snd_config_add(slaveConfig, elemConfig); + if (params->rate) { + error += snd_config_imake_integer(&elemConfig, "rate", params->rate); + error += snd_config_add(slaveConfig, elemConfig); + } + if (params->format) { + error += snd_config_imake_string(&elemConfig, "format", params->formatS); error += snd_config_add(slaveConfig, elemConfig); - // *** error += snd_config_imake_integer(&elemConfig, "rate", pcmSlave->params.rate); - error += snd_config_imake_integer(&elemConfig, "rate", 48000); } - error += snd_config_add(slaveConfig, elemConfig); if (error) goto OnErrorExit; // add leaf into config error += snd_config_add(rateConfig, slaveConfig); if (error) goto OnErrorExit; - - error += snd_config_search(snd_config, "pcm", &pcmConfig); + + error += snd_config_search(snd_config, "pcm", &pcmConfig); error += snd_config_add(pcmConfig, rateConfig); if (error) { - AFB_ApiError(source->api, "AlsaCreateRate: fail to add configRATE=%s", pcmPlug->cardid); + AFB_ApiError(mixer->api, "AlsaCreateRate: fail to add configRATE=%s", pcmPlug->cid.cardid); goto OnErrorExit; } - - if (open) error = _snd_pcm_rate_open(&pcmPlug->handle, pcmPlug->cardid, snd_config, rateConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); + + if (open) error = _snd_pcm_rate_open(&pcmPlug->handle, pcmPlug->cid.cardid, snd_config, rateConfig, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (error) { - AFB_ApiError(source->api, "AlsaCreateRate: fail to create Rate=%s Slave=%s Error=%s", pcmPlug->cardid, pcmSlave->cardid, snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaCreateRate: fail to create Rate=%s Slave=%s Error=%s", pcmPlug->cid.cardid, pcmSlave->cid.cardid, snd_strerror(error)); goto OnErrorExit; } - + // Debug config & pcm - AlsaDumpCtlConfig (source, "plug-rate", pcmConfig, 1); - //AlsaDumpCtlConfig (source, "plug-rate", rateConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateRate: %s done\n", pcmPlug->cardid); + AlsaDumpCtlConfig(mixer, "plug-rate", pcmConfig, 1); + //AlsaDumpCtlConfig (mixer, "plug-rate", rateConfig, 1); + AFB_ApiNotice(mixer->api, "AlsaCreateRate: %s done\n", pcmPlug->cid.cardid); return pcmPlug; OnErrorExit: - AlsaDumpCtlConfig(source, "plug-rate", rateConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateRate: OnErrorExit\n"); + AlsaDumpCtlConfig(mixer, "plug-rate", rateConfig, 1); + AFB_ApiNotice(mixer->api, "AlsaCreateRate: 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 index f254c68..ac3aa1c 100644 --- a/plugins/alsa/alsa-plug-route.c +++ b/plugins/alsa/alsa-plug-route.c @@ -22,122 +22,163 @@ ALSA_PLUG_PROTO(route); -STATIC int CardChannelByUid(CtlSourceT *source, AlsaPcmInfoT *pcmBackend, const char *uid) { - int channelIdx = -1; +typedef struct { + const char *uid; + const char *cardid; + int cardidx; + int ccount; + int port; +} ChannelCardPortT; - // 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++) { - AlsaPcmChannelT *channels = pcmBackend[cardIdx].channels; +STATIC int CardChannelByUid(SoftMixerT *mixer, const char *uid, ChannelCardPortT *response) { + + // 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; + + AlsaPcmChannelT **channels = mixer->sinks[idx]->channels; if (!channels) { - AFB_ApiError(source->api, "CardChannelByUid: No Backend card=%s [should declare channels]", pcmBackend[cardIdx].uid); + AFB_ApiError(mixer->api, "CardChannelByUid: No Sink card=%s [should declare channels]", mixer->sinks[idx]->uid); goto OnErrorExit; } - for (int idx = 0; channels[idx].uid != NULL; idx++) { - if (!strcmp(channels[idx].uid, uid)) return targetIdx; - targetIdx++; + for (jdx = 0; jdx < mixer->sinks[idx]->ccount; jdx++) { + if (!strcasecmp(channels[jdx]->uid, uid)) { + response->port = channels[jdx]->port; + response->uid = mixer->sinks[idx]->uid; + response->ccount = mixer->sinks[idx]->ccount; + response->cardid = mixer->sinks[idx]->sndcard->cid.cardid; + response->cardidx = mixer->sinks[idx]->sndcard->cid.cardidx; + break; + } + } + + if (jdx == mixer->sinks[idx]->ccount) { + AFB_ApiError(mixer->api, "CardChannelByUid: No Channel with uid=%s [should declare channels]", uid); + goto OnErrorExit; } } - // this is OnErrorExit - return channelIdx; + return 0; OnErrorExit: return -1; } -PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone, int open) { - SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; +PUBLIC AlsaPcmCtlT* AlsaCreateRoute(SoftMixerT *mixer, AlsaSndZoneT *zone, int open) { 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; - - assert(mixerHandle); + int scount=0, error = 0; + ChannelCardPortT slave, channel; + AlsaPcmCtlT *pcmRoute = calloc(1, sizeof (AlsaPcmCtlT)); - AlsaPcmInfoT *pcmBackend = mixerHandle->backend; - AlsaPcmInfoT* pcmSlave=mixerHandle->multiPcm; - if (!pcmBackend || !pcmSlave) { - AFB_ApiError(source->api, "AlsaCreateRoute: mixer=%s zone(%s)(zone) No Sound Card Ctl find [should Registry snd_cards first]" - , mixerHandle->uid, zone->uid); + 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; } - // 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; + char *cardid; + (void) asprintf(&cardid, "route-%s", zone->uid); + 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 + char *dmixUid; + (void) asprintf(&dmixUid, "dmix-%s", slave.uid); + + // 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; - // 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))); + // We create 1st ttable to retrieve sndcard slave and channel count + (void)snd_config_make_compound(&tableConfig, "ttable", 0); - // loop on sound card to include into multi - for (int idx = 0; zone->channels[idx].uid != NULL; idx++) { + 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; + } - 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); + 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 channel = zone->channels[idx].port; + + 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[channel]) { - pcmPlug->ccount++; + if (!cports[port]) { + zcount++; 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]); + 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[channel], elemConfig); + error += snd_config_add(cports[port], elemConfig); if (error) goto OnErrorExit; } + if (error) goto OnErrorExit; - // update top config to access previous plugin PCM + // 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); + error += snd_config_set_id(routeConfig, 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", dmixUid); + error += snd_config_add(slaveConfig, elemConfig); +// error += snd_config_imake_integer(&elemConfig, "channels", scount); +// error += snd_config_add(slaveConfig, elemConfig); + error += snd_config_add(routeConfig, slaveConfig); + error += snd_config_add(routeConfig, tableConfig); + if (error) goto OnErrorExit; - if (open) error = _snd_pcm_route_open(&pcmPlug->handle, zone->uid, snd_config, routeConfig, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + 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(source->api, "AlsaCreateRoute:zone(%s) fail to create Plug=%s error=%s", zone->uid, pcmPlug->cardid, snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaCreateRoute:zone(%s) fail to create Plug=%s error=%s", 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(source->api, "AlsaCreateDmix:%s fail to add configDMIX=%s", zone->uid, pcmPlug->cardid); + AFB_ApiError(mixer->api, "AlsaCreateDmix:%s fail to add config route=%s error=%s", zone->uid, pcmRoute->cid.cardid, snd_strerror(error)); goto OnErrorExit; } // Debug config & pcm - //AlsaDumpCtlConfig(source, "plug-route", routeConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateRoute:zone(%s) done\n", zone->uid); - return pcmPlug; + AFB_ApiNotice(mixer->api, "AlsaCreateRoute:zone(%s) done", zone->uid); + AlsaDumpCtlConfig(mixer, "plug-route", routeConfig, 1); + return pcmRoute; OnErrorExit: - AlsaDumpCtlConfig(source, "plug-route", routeConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateRoute:zone(%s) OnErrorExit\n", zone->uid); + free(pcmRoute); + AlsaDumpCtlConfig(mixer, "plug-pcm", snd_config, 1); + AlsaDumpCtlConfig(mixer, "plug-route", routeConfig, 1); + AFB_ApiNotice(mixer->api, "AlsaCreateRoute:zone(%s) OnErrorExit\n", zone->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 index cb764ef..f16b0a9 100644 --- a/plugins/alsa/alsa-plug-vol.c +++ b/plugins/alsa/alsa-plug-vol.c @@ -22,61 +22,30 @@ ALSA_PLUG_PROTO(softvol); // stream uses solftvol plugin -STATIC AlsaPcmInfoT* SlaveZoneByUid(CtlSourceT *source, AlsaPcmInfoT **pcmZones, const char *uid) { - AlsaPcmInfoT *slaveZone= NULL; - - // Loop on every Registryed 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* AlsaCreateSoftvol(CtlSourceT *source, AlsaLoopStreamT *stream, AlsaPcmInfoT *ctlControl, const char* ctlName, int max, int open) { - SoftMixerHandleT *mixerHandle = (SoftMixerHandleT*) source->context; +PUBLIC AlsaPcmCtlT* AlsaCreateSoftvol(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaSndZoneT *zone, AlsaSndCtlT *sndcard, char* ctlName, int max, int open) { snd_config_t *streamConfig, *elemConfig, *slaveConfig, *controlConfig,*pcmConfig; + AlsaPcmCtlT *pcmVol= calloc(1,sizeof(AlsaPcmCtlT)); int error = 0; - AlsaPcmInfoT *pcmPlug= calloc(1,sizeof(AlsaPcmInfoT)); - - assert (mixerHandle); - - // assert static/global softmixer handle get requited info - AlsaSndLoopT *ctlLoop = mixerHandle->frontend; - if (!ctlLoop) { - AFB_ApiError(source->api, "AlsaCreateSoftvol:%s(stream) No Loop found [should Registry snd_loop first]",stream->uid); - goto OnErrorExit; - } // assert static/global softmixer handle get requited info - AlsaPcmInfoT **pcmZones = mixerHandle->routes; + AlsaSndZoneT **pcmZones = mixer->zones; if (!pcmZones) { - AFB_ApiError(source->api, "AlsaCreateSoftvol:%s(stream) No Zone found [should Registry snd_zones first]", stream->uid); + AFB_ApiError(mixer->api, "AlsaCreateSoftvol:%s(stream) No Zone found [should Registry zones first]", stream->uid); goto OnErrorExit; } + + char *cardid; + (void) asprintf(&cardid, "softvol-%s", stream->uid); + pcmVol->cid.cardid = (const char *) cardid; - // search for target zone uid - AlsaPcmInfoT *pcmSlave= SlaveZoneByUid (source, pcmZones, stream->zone); - if (!pcmSlave || !pcmSlave->uid) { - AFB_ApiError(source->api, "AlsaCreateSoftvol:%s(stream) fail to find Zone=%s", stream->uid, stream->zone); - goto OnErrorExit; - } + char *slaveid; + (void)asprintf(&slaveid, "route-%s", zone->uid); + // Fulup debug (void) asprintf(&slaveid, "dmix-%s", "8CH-USB"); - // stream inherit from zone channel count - pcmPlug->uid= strdup(stream->uid); - pcmPlug->cardid= pcmPlug->uid; - pcmPlug->devpath=NULL; - pcmPlug->ccount= pcmSlave->ccount; - memcpy (&pcmPlug->params, &stream->params, sizeof(AlsaPcmHwInfoT)); - pcmPlug->params.channels= 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_set_id (streamConfig, pcmVol->cid.cardid); error += snd_config_imake_string(&elemConfig, "type", "softvol"); error += snd_config_add(streamConfig, elemConfig); error += snd_config_imake_integer(&elemConfig, "resolution", max+1); @@ -85,7 +54,7 @@ PUBLIC AlsaPcmInfoT* AlsaCreateSoftvol(CtlSourceT *source, AlsaLoopStreamT *stre // add slave leaf error += snd_config_make_compound(&slaveConfig, "slave", 0); - error += snd_config_imake_string(&elemConfig, "pcm", pcmSlave->cardid); + error += snd_config_imake_string(&elemConfig, "pcm", slaveid); error += snd_config_add(slaveConfig, elemConfig); error += snd_config_add(streamConfig, slaveConfig); if (error) goto OnErrorExit; @@ -94,34 +63,36 @@ PUBLIC AlsaPcmInfoT* AlsaCreateSoftvol(CtlSourceT *source, AlsaLoopStreamT *stre error += snd_config_make_compound(&controlConfig, "control", 0); error += snd_config_imake_string(&elemConfig, "name", ctlName); error += snd_config_add(controlConfig, elemConfig); - error += snd_config_imake_integer(&elemConfig, "card", ctlControl->cardidx); + error += snd_config_imake_integer(&elemConfig, "card", sndcard->cid.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(); - - if (open) error = _snd_pcm_softvol_open(&pcmPlug->handle, stream->uid, snd_config, streamConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); + if (open) error = _snd_pcm_softvol_open(&pcmVol->handle, stream->uid, snd_config, streamConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK); if (error) { - AFB_ApiError(source->api, "AlsaCreateSoftvol:%s(stream) fail to create Plug=%s Slave=%s error=%s", stream->uid, pcmPlug->cardid, pcmSlave->cardid, snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaCreateSoftvol:%s(stream) fail to create Plug=%s Slave=%s error=%s", stream->uid, pcmVol->cid.cardid, sndcard->cid.cardid, snd_strerror(error)); goto OnErrorExit; } + // update top config to access previous plugin PCM + snd_config_update(); + error += snd_config_search(snd_config, "pcm", &pcmConfig); error += snd_config_add(pcmConfig, streamConfig); if (error) { - AFB_ApiError(source->api, "AlsaCreateSoftvol:%s(stream) fail to add config", stream->uid); + AFB_ApiError(mixer->api, "AlsaCreateSoftvol:%s(stream) fail to add config error=%s", stream->uid, snd_strerror(error)); goto OnErrorExit; } // Debug config & pcm - //AlsaDumpCtlConfig (source, "plug-stream", streamConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateSoftvol:%s(stream) done\n", stream->uid); - return pcmPlug; + //AlsaDumpCtlConfig (mixer, "plug-config", pcmConfig, 1); + //AlsaDumpCtlConfig(mixer, "plug-softvol", streamConfig, 1); + AFB_ApiNotice(mixer->api, "AlsaCreateSoftvol:%s(stream) done\n", stream->uid); + return pcmVol; OnErrorExit: - AlsaDumpCtlConfig(source, "plug-stream", streamConfig, 1); - AFB_ApiNotice(source->api, "AlsaCreateSoftvol:%s(stream) OnErrorExit\n", stream->uid); + AlsaDumpCtlConfig (mixer, "plug-config", pcmConfig, 1); + AlsaDumpCtlConfig(mixer, "plug-softvol", streamConfig, 1); + AFB_ApiNotice(mixer->api, "AlsaCreateSoftvol:%s(stream) OnErrorExit\n", stream->uid); return NULL; }
\ No newline at end of file diff --git a/plugins/alsa/alsa-softmixer.h b/plugins/alsa/alsa-softmixer.h index a8d7af6..f8d94e4 100644 --- a/plugins/alsa/alsa-softmixer.h +++ b/plugins/alsa/alsa-softmixer.h @@ -40,14 +40,21 @@ #define STATIC static #endif +#define MAINLOOP_CONCURENCY 0 #define MAINLOOP_WATCHDOG 30000 -#define MAX_AUDIO_STREAMS 8*2 #define ALSA_DEFAULT_PCM_RATE 48000 #define ALSA_DEFAULT_PCM_VOLUME 80 #define ALSA_BUFFER_FRAMES_COUNT 1024 #define ALSA_CARDID_MAX_LEN 64 +#define SMIXER_SUBDS_CTLS 3 +#define SMIXER_DEFLT_LOOPS 4 +#define SMIXER_DEFLT_SINKS 8 +#define SMIXER_DEFLT_SOURCES 32 +#define SMIXER_DEFLT_ZONES 32 +#define SMIXER_DEFLT_STREAMS 32 +#define SMIXER_DEFLT_RAMPS 8 #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) @@ -59,7 +66,6 @@ #define STATIC static #endif - typedef enum { FONTEND_NUMID_IGNORE, FONTEND_NUMID_PAUSE, @@ -82,11 +88,13 @@ typedef struct { } AlsaPcmCopyHandleT; typedef struct { - const char *uid; - int delay; // delay between volset in us - int stepDown; // linear % - int stepUp; // linear % -} AlsaVolRampT; + const char*name; + int numid; + int count; + long min; + long max; + long step; +} AlsaSndControlT; typedef struct { const char*uid; @@ -96,6 +104,7 @@ typedef struct { typedef struct { unsigned int rate; unsigned int channels; + const char *formatS; snd_pcm_format_t format; snd_pcm_access_t access; size_t sampleSize; @@ -103,126 +112,175 @@ typedef struct { typedef struct { const char *uid; + int delay; // delay between volset in us + int stepDown; // linear % + int stepUp; // linear % +} AlsaVolRampT; + +typedef struct { + int cardidx; const char *devpath; const char *cardid; - int cardidx; + const char *name; + const char *longname; int device; int subdev; - int numid; +} AlsaDevInfoT; + +typedef struct { int ccount; + AlsaDevInfoT cid; snd_pcm_t *handle; - AlsaPcmChannelT *channels; - AlsaPcmHwInfoT params; -} AlsaPcmInfoT; + AlsaPcmHwInfoT *params; +} AlsaPcmCtlT; typedef struct { - const char *uid; - snd_pcm_stream_t type; - AlsaPcmChannelT *channels; - AlsaPcmInfoT *pcm; -} AlsaSndZoneT; + int numid; + RegistryNumidT type; + AlsaPcmCtlT *pcm; +} RegistryEntryPcmT; + +typedef struct { + long rcount; + AlsaDevInfoT cid; + snd_ctl_t *ctl; + AlsaPcmHwInfoT *params; + RegistryEntryPcmT **registry; +} AlsaSndCtlT; typedef struct { - AlsaPcmInfoT *pcm; - int numid; - RegistryNumidT type; -} RegistryStreamT; + const char *uid; + AlsaPcmChannelT **sources; + AlsaPcmChannelT **sinks; + int ccount; + AlsaPcmHwInfoT *params; +} AlsaSndZoneT; typedef struct { - RegistryStreamT stream[MAX_AUDIO_STREAMS + 1]; - int count; - snd_ctl_t *ctlDev; -} RegistryHandleT; + const char *uid; + unsigned int ccount; + AlsaSndCtlT *sndcard; + AlsaSndControlT volume; + AlsaSndControlT mute; + AlsaPcmChannelT **channels; + snd_pcm_stream_t direction; +} AlsaSndPcmT; +typedef struct { + const char*uid; + int index; + int numid; +} AlsaLoopSubdevT; typedef struct { const char *uid; - const char *devpath; - const char *cardid; - int cardidx; int playback; int capture; - int scount; - AlsaPcmInfoT *subdevs; - AlsaVolRampT *ramps; - RegistryHandleT *registry; + long scount; + AlsaSndCtlT *sndcard; + AlsaLoopSubdevT **subdevs; } AlsaSndLoopT; -typedef struct { +typedef struct { const char *uid; const char *info; - const char *zone; + const char *sink; + const char *source; const char *ramp; int volume; int mute; - AlsaPcmInfoT *pcm; - AlsaPcmHwInfoT params; - AlsaPcmCopyHandleT copy; -} AlsaLoopStreamT; + AlsaPcmHwInfoT *params; + AlsaPcmCopyHandleT *copy; +} AlsaStreamAudioT; typedef struct { const char *uid; const char *info; - AlsaSndLoopT *frontend; - AlsaPcmInfoT *backend; - AlsaPcmInfoT *multiPcm; - AlsaPcmInfoT **routes; - AlsaLoopStreamT *streams; -} SoftMixerHandleT; + AFB_ApiT api; + sd_event *sdLoop; + + struct { + unsigned int loops; + unsigned int sinks; + unsigned int sources; + unsigned int zones; + unsigned int streams; + unsigned int ramps; + } max; + AlsaSndLoopT **loops; + AlsaSndPcmT **sinks; + AlsaSndPcmT **sources; + AlsaSndZoneT **zones; + AlsaStreamAudioT **streams; + AlsaVolRampT **ramps; +} SoftMixerT; // alsa-utils-bypath.c -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_card_info_t *AlsaByPathInfo(SoftMixerT *mixer, const char *devpath); +PUBLIC AlsaPcmCtlT *AlsaByPathOpenPcm(SoftMixerT *mixer, AlsaDevInfoT *pcmId, snd_pcm_stream_t direction); +PUBLIC snd_ctl_t *AlsaByPathOpenCtl(SoftMixerT *mixer, const char *uid, AlsaSndCtlT *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 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, 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)) +PUBLIC json_object *AlsaDumpObjF(const char *format, ...); +PUBLIC char *AlsaDumpPcmUid(snd_pcm_t *pcmHandle, char *buffer, size_t len); +PUBLIC char *AlsaDumpCtlUid(snd_ctl_t *ctlHandle, char *buffer, size_t len); +PUBLIC void AlsaDumpFormats(SoftMixerT *mixer, snd_pcm_t *pcmHandle); +PUBLIC void AlsaDumpCtlSubdev(SoftMixerT *mixer, snd_ctl_t *handle); +PUBLIC void AlsaDumpPcmParams(SoftMixerT *mixer, snd_pcm_hw_params_t *pcmHwParams); +PUBLIC void AlsaDumpPcmInfo(SoftMixerT *mixer, const char* info, snd_pcm_t *pcm); +PUBLIC void AlsaDumpElemConfig(SoftMixerT *mixer, const char* info, const char* elem); +PUBLIC void AlsaDumpCtlConfig(SoftMixerT *mixer, const char* info, snd_config_t *config, int indent); // alsa-core-ctl.c -PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(CtlSourceT *source, const char *cardid); -PUBLIC snd_ctl_t *AlsaCtlOpenCtl(CtlSourceT *source, const char *cardid); -PUBLIC snd_ctl_t* AlsaCrlFromPcm(CtlSourceT *source, snd_pcm_t *pcm); -PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t * ctlDev, RegistryHandleT *registry); -PUBLIC int AlsaCtlRegister(CtlSourceT *source, SoftMixerHandleT *mixer, AlsaPcmInfoT *pcm, RegistryNumidT type, int numid); -PUBLIC int AlsaCtlNumidGetLong(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long* value); -PUBLIC int AlsaCtlNumidSetLong(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long value); -PUBLIC int AlsaCtlNameGetLong(CtlSourceT *source, snd_ctl_t* ctlDev, const char *ctlName, long* value); -PUBLIC int AlsaCtlNameSetLong(CtlSourceT *source, snd_ctl_t* ctlDev, const char *ctlName, long value); -PUBLIC int AlsaCtlCreateControl(CtlSourceT *source, snd_ctl_t* ctlDev, AlsaPcmInfoT *subdevs, char* name, int ctlCount, int ctlMin, int ctlMax, int ctlStep, long value); -PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(CtlSourceT *source, snd_ctl_t* ctlDev, const char *ctlName); -PUBLIC int CtlElemIdSetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, long value); -PUBLIC int CtlElemIdGetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, long *value); +PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNumidElemId(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numid) ; +PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName) ; +PUBLIC snd_ctl_t *AlsaCtlOpenCtl(SoftMixerT *mixer, const char *cardid) ; +PUBLIC int CtlElemIdGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, snd_ctl_elem_id_t *elemId, long *value) ; +PUBLIC int CtlElemIdSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, snd_ctl_elem_id_t *elemId, long value) ; +PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(SoftMixerT *mixer, const char *cardid) ; +PUBLIC int AlsaCtlNumidSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numid, long value) ; +PUBLIC int AlsaCtlNumidGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, int numid, long* value) ; +PUBLIC int AlsaCtlNameSetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName, long value) ; +PUBLIC int AlsaCtlNameGetLong(SoftMixerT *mixer, AlsaSndCtlT *sndcard, const char *ctlName, long* value) ; +PUBLIC int AlsaCtlCreateControl(SoftMixerT *mixer, AlsaSndCtlT *sndcard, char* ctlName, int ctlCount, int ctlMin, int ctlMax, int ctlStep, long value) ; +PUBLIC snd_ctl_t* AlsaCrlFromPcm(SoftMixerT *mixer, snd_pcm_t *pcm) ; +PUBLIC int AlsaCtlSubscribe(SoftMixerT *mixer, const char *uid, AlsaSndCtlT *sndcard) ; +PUBLIC int AlsaCtlRegister(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaPcmCtlT *pcmdev, RegistryNumidT type, int numid); // alsa-core-pcm.c -PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *opts); -PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaLoopStreamT *loopStream, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pcmOut, AlsaPcmHwInfoT * opts); +PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, AlsaPcmHwInfoT *opts); +PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT *pcmIn, AlsaPcmCtlT *pcmOut, AlsaPcmHwInfoT * opts); + // alsa-plug-*.c _snd_pcm_PLUGIN_open_ see macro ALSA_PLUG_PROTO(plugin) -PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave, int open); -PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmName, int open); -PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone, int open); -PUBLIC AlsaPcmInfoT* AlsaCreateSoftvol(CtlSourceT *source, AlsaLoopStreamT *stream, AlsaPcmInfoT *ctlControl, const char* ctlName, int max, int open); -PUBLIC AlsaPcmInfoT* AlsaCreateRate(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave, int open); +PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, AlsaPcmHwInfoT *opts); +PUBLIC int AlsaPcmCopy(SoftMixerT *mixer, AlsaStreamAudioT *streamAudio, AlsaPcmCtlT *pcmIn, AlsaPcmCtlT *pcmOut, AlsaPcmHwInfoT * opts); +PUBLIC AlsaPcmCtlT* AlsaCreateSoftvol(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaSndZoneT *zone, AlsaSndCtlT *sndcard, char* ctlName, int max, int open); +PUBLIC AlsaPcmCtlT* AlsaCreateRoute(SoftMixerT *mixer, AlsaSndZoneT *zone, int open); +PUBLIC AlsaPcmCtlT* AlsaCreateRate(SoftMixerT *mixer, const char* pcmName, AlsaPcmCtlT *pcmSlave, AlsaPcmHwInfoT *params, int open); +PUBLIC AlsaPcmCtlT* AlsaCreateDmix(SoftMixerT *mixer, const char* pcmName, AlsaSndPcmT *pcmSlave, int open); // alsa-api-* -PUBLIC int ProcessSndParams(CtlSourceT *source, const char* uid, json_object *paramsJ, AlsaPcmHwInfoT *params); -PUBLIC int SndFrontend (CtlSourceT *source, json_object *argsJ); -PUBLIC int SndBackend (CtlSourceT *source, json_object *argsJ); -PUBLIC int SndZones (CtlSourceT *source, json_object *argsJ); -PUBLIC int LoopStreams(CtlSourceT *source, json_object *argsJ, json_object **responseJ); +PUBLIC AlsaLoopSubdevT *ApiLoopFindSubdev(SoftMixerT *mixer, const char *streamUid, const char *targetUid, AlsaSndLoopT **loop); +PUBLIC int ApiLoopAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ); +PUBLIC AlsaPcmHwInfoT *ApiPcmSetParams(SoftMixerT *mixer, const char *uid, json_object *paramsJ); +PUBLIC AlsaSndPcmT *ApiPcmAttachOne(SoftMixerT *mixer, const char *uid, snd_pcm_stream_t direction, json_object *argsJ); +PUBLIC AlsaVolRampT *ApiRampGetByUid(SoftMixerT *mixer, const char *uid); +PUBLIC int ApiRampAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object *argsJ); +PUBLIC AlsaPcmHwInfoT *ApiSinkGetParamsByZone(SoftMixerT *mixer, const char *target); +PUBLIC int ApiSinkAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ); +PUBLIC AlsaSndCtlT *ApiSourceFindSubdev(SoftMixerT *mixer, const char *target); +PUBLIC int ApiSourceAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ); +PUBLIC json_object *CreateOneStream(SoftMixerT *mixer, AlsaStreamAudioT *stream); +PUBLIC int ApiStreamAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ, json_object **responseJ); +PUBLIC AlsaSndZoneT *ApiZoneGetByUid(SoftMixerT *mixer, const char *target); +PUBLIC int ApiZoneAttach(SoftMixerT *mixer, AFB_ReqT request, const char *uid, json_object * argsJ); // alsa-effect-ramp.c -PUBLIC int AlsaVolRampApply(CtlSourceT *source, AlsaSndLoopT *frontend, AlsaLoopStreamT *stream, AlsaVolRampT *ramp, json_object *volumeJ); +PUBLIC AlsaVolRampT *ApiRampGetByUid(SoftMixerT *mixer, const char *uid); +PUBLIC int AlsaVolRampApply(SoftMixerT *mixer, AlsaSndCtlT *sndcard, AlsaStreamAudioT *stream, json_object *rampJ); #endif
\ No newline at end of file diff --git a/plugins/alsa/alsa-utils-bypath.c b/plugins/alsa/alsa-utils-bypath.c index a857ab1..30e528f 100644 --- a/plugins/alsa/alsa-utils-bypath.c +++ b/plugins/alsa/alsa-utils-bypath.c @@ -35,7 +35,7 @@ // Clone of AlsaLib snd_card_load2 static function -PUBLIC snd_ctl_card_info_t *AlsaByPathInfo(CtlSourceT *source, const char *devpath) { +PUBLIC snd_ctl_card_info_t *AlsaByPathInfo(SoftMixerT *mixer, const char *devpath) { int open_dev; snd_ctl_card_info_t *cardInfo = malloc(snd_ctl_card_info_sizeof()); @@ -54,104 +54,72 @@ PUBLIC snd_ctl_card_info_t *AlsaByPathInfo(CtlSourceT *source, const char *devpa return cardInfo; OnErrorExit: - AFB_ApiError(source->api, "AlsaCardInfoByPath: fail to find sndcard by path= %s", devpath); + AFB_ApiError(mixer->api, "AlsaCardInfoByPath: fail to find sndcard by path= %s", devpath); return NULL; } -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); - 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); - } - - if (!cardInfo) { - 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->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); +PUBLIC AlsaPcmCtlT *AlsaByPathOpenPcm(SoftMixerT *mixer, AlsaDevInfoT *pcmDev, snd_pcm_stream_t direction) { + int error; + AlsaPcmCtlT *pcmCtl = calloc(1, sizeof (AlsaPcmCtlT)); + + if (!pcmDev->cardid) { + char *cardid; + if (pcmDev->subdev) (void)asprintf(&cardid, "hw:%i,%i,%i", pcmDev->cardidx, pcmDev->device, pcmDev->subdev); + else if (pcmDev->device) (void) asprintf(&cardid, "hw:%i,%i", pcmDev->cardidx, pcmDev->device); + else (void) asprintf(&cardid, "hw:%i", pcmDev->cardidx); + pcmDev->cardid= (const char*)cardid; } - - // make sure UID will cannot be removed - dev->uid= strdup(dev->uid); - return 0; -OnErrorExit: - return -1; -} + // inherit CID fropm pcmDev + pcmCtl->cid.cardid = pcmDev->cardid; + pcmCtl->cid.cardidx = pcmDev->cardidx; + pcmCtl->cid.device = pcmDev->device; + pcmCtl->cid.subdev = pcmDev->subdev; + pcmCtl->cid.name=NULL; + pcmCtl->cid.longname=NULL; -PUBLIC AlsaPcmInfoT* AlsaByPathOpenPcm(CtlSourceT *source, AlsaPcmInfoT *dev, snd_pcm_stream_t direction) { - int error; - - // 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(&pcm->handle, pcm->cardid, direction, SND_PCM_NONBLOCK); + error = snd_pcm_open(&pcmCtl->handle, pcmCtl->cid.cardid, direction, SND_PCM_NONBLOCK); if (error) { - AFB_ApiError(source->api, "AlsaByPathOpenPcm: fail openpcm (cardid=%s idxdev=%i subdev=%d): %s" - , pcm->cardid, pcm->device, pcm->subdev, snd_strerror(error)); + AFB_ApiError(mixer->api, "AlsaByPathOpenPcm: fail openpcm cardid=%s error=%s", pcmCtl->cid.cardid, snd_strerror(error)); goto OnErrorExit; } - return (pcm); + return pcmCtl; OnErrorExit: + free(pcmCtl); return NULL; } -PUBLIC snd_ctl_t *AlsaByPathOpenCtl(CtlSourceT *source, AlsaPcmInfoT *dev) { +PUBLIC snd_ctl_t *AlsaByPathOpenCtl(SoftMixerT *mixer, const char *uid, AlsaSndCtlT *dev) { int err; - 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->cardid) cardInfo = AlsaCtlGetInfo(source, dev->cardid); + if (dev->cid.devpath) cardInfo = AlsaByPathInfo(mixer, dev->cid.devpath); + else if (dev->cid.cardid) cardInfo = AlsaCtlGetInfo(mixer, dev->cid.cardid); if (!cardInfo) { - AFB_ApiError(source->api, "AlsaByPathOpenCtl: fail to find sndcard by path=%s id=%s", dev->devpath, dev->cardid); + AFB_ApiError(mixer->api, "AlsaByPathOpenCtl: uid=%s fail to find sndcard by path=%s id=%s", uid, dev->cid.devpath, dev->cid.cardid); goto OnErrorExit; } // extract useful info from cardInfo handle - 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); + dev->cid.devpath = NULL; + dev->cid.cardidx = snd_ctl_card_info_get_card(cardInfo); + dev->cid.name = strdup(snd_ctl_card_info_get_name(cardInfo)); + dev->cid.longname = strdup(snd_ctl_card_info_get_longname(cardInfo)); // build a valid name and open sndcard - 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)); + (void) asprintf((char**) &dev->cid.cardid, "hw:%i", dev->cid.cardidx); + + if ((err = snd_ctl_open(&handle, dev->cid.cardid, 0)) < 0) { + AFB_ApiError(mixer->api, "AlsaByPathOpenCtl uid=%s sndcard open fail cardid=%s longname=%s error=%s", uid, dev->cid.cardid, dev->cid.longname, snd_strerror(err)); goto OnErrorExit; } - AFB_ApiNotice(source->api, "AlsaCtlOpenByPath: sndcard hw:%d id=%s name=%s", cardIndex, cardId, cardName); + AFB_ApiNotice(mixer->api, "AlsaCtlOpenByPath: uid=%s cardid=%s cardname=%s", uid, dev->cid.cardid, dev->cid.longname); free(cardInfo); return handle; diff --git a/plugins/alsa/alsa-utils-dump.c b/plugins/alsa/alsa-utils-dump.c index 9ff07d8..3359a39 100644 --- a/plugins/alsa/alsa-utils-dump.c +++ b/plugins/alsa/alsa-utils-dump.c @@ -18,8 +18,22 @@ #define _GNU_SOURCE // needed for vasprintf - #include "alsa-softmixer.h" +#include <stdarg.h> + +PUBLIC json_object *AlsaDumpObjF(const char *format, ...) { + assert (format); + va_list args; + + char *result; + va_start(args, format); + int error= vasprintf(&result, format, args); + va_end(args); + + if (error < 0) return NULL; + + return (json_object_new_string(result)); +} PUBLIC char *AlsaDumpPcmUid(snd_pcm_t *pcmHandle, char *buffer, size_t len) { snd_pcm_info_t *pcmInfo; @@ -56,7 +70,7 @@ OnErrorExit: return NULL; } -PUBLIC void AlsaDumpFormats(CtlSourceT *source, snd_pcm_t *pcmHandle) { +PUBLIC void AlsaDumpFormats(SoftMixerT *mixer, snd_pcm_t *pcmHandle) { char string[32]; snd_pcm_format_t format; snd_pcm_hw_params_t *pxmHwParams; @@ -65,15 +79,15 @@ PUBLIC void AlsaDumpFormats(CtlSourceT *source, snd_pcm_t *pcmHandle) { snd_pcm_hw_params_alloca(&pxmHwParams); snd_pcm_hw_params_any(pcmHandle, pxmHwParams); - AFB_ApiNotice(source->api, "Available formats: PCM=%s", ALSA_PCM_UID(pcmHandle, string)); + AFB_ApiNotice(mixer->api, "Available formats: PCM=%s", ALSA_PCM_UID(pcmHandle, string)); for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) { if (snd_pcm_hw_params_test_format(pcmHandle, pxmHwParams, format) == 0) { - AFB_ApiNotice(source->api, "- %s", snd_pcm_format_name(format)); + AFB_ApiNotice(mixer->api, "- %s", snd_pcm_format_name(format)); } } } -PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) { +PUBLIC void AlsaDumpCtlSubdev(SoftMixerT *mixer, snd_ctl_t *handle) { snd_ctl_card_info_t *cardInfo; int err; int dev = -1; @@ -91,7 +105,7 @@ PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) { while (1) { if (snd_ctl_pcm_next_device(handle, &dev) < 0) { - AFB_ApiError(source->api, "AlsaDumpCard: fail to open subdev card id=%s name=%s", cardId, cardName); + AFB_ApiError(mixer->api, "AlsaDumpCard: fail to open subdev card id=%s name=%s", cardId, cardName); goto OnErrorExit; } @@ -101,11 +115,11 @@ PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) { // ignore empty device slot if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { if (err != -ENOENT) - AFB_ApiError(source->api, "control digital audio info (%s): %s", cardName, snd_strerror(err)); + AFB_ApiError(mixer->api, "control digital audio info (%s): %s", cardName, snd_strerror(err)); continue; } - AFB_ApiNotice(source->api, "AlsaDumpCard card %d: %s [%s], device %d: %s [%s]", + AFB_ApiNotice(mixer->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 @@ -115,12 +129,12 @@ PUBLIC void AlsaDumpCtlSubdev(CtlSourceT *source, snd_ctl_t *handle) { for (unsigned int idx = 0; idx < subdevCount; idx++) { snd_pcm_info_set_subdevice(pcminfo, idx); if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { - AFB_ApiError(source->api, "AlsaDumpCard: control digital audio playback info %i: %s", cardIndex, snd_strerror(err)); + AFB_ApiError(mixer->api, "AlsaDumpCard: control digital audio playback info %i: %s", cardIndex, snd_strerror(err)); } else { - AFB_ApiNotice(source->api, "AlsaDumpCard: -- Subdevice #%d: %s", idx, snd_pcm_info_get_subdevice_name(pcminfo)); + AFB_ApiNotice(mixer->api, "AlsaDumpCard: -- Subdevice #%d: %s", idx, snd_pcm_info_get_subdevice_name(pcminfo)); } } - AFB_ApiNotice(source->api, "AlsaDumpCard => subdevice count=%d avaliable=%d", subdevCount, subdevAvail); + AFB_ApiNotice(mixer->api, "AlsaDumpCard => subdevice count=%d avaliable=%d", subdevCount, subdevAvail); } return; @@ -128,18 +142,18 @@ OnErrorExit: return; } -PUBLIC void AlsaDumpPcmParams(CtlSourceT *source, snd_pcm_hw_params_t *pcmHwParams) { +PUBLIC void AlsaDumpPcmParams(SoftMixerT *mixer, snd_pcm_hw_params_t *pcmHwParams) { snd_output_t *output; char *buffer; snd_output_buffer_open(&output); snd_pcm_hw_params_dump(pcmHwParams, output); snd_output_buffer_string(output, &buffer); - AFB_ApiNotice(source->api, "AlsaPCMDump: %s", buffer); + AFB_ApiNotice(mixer->api, "AlsaPCMDump: %s", buffer); snd_output_close(output); } -PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, const char* info, snd_pcm_t *pcm) { +PUBLIC void AlsaDumpPcmInfo(SoftMixerT *mixer, const char* info, snd_pcm_t *pcm) { snd_output_t *out; char *buffer; @@ -150,18 +164,18 @@ PUBLIC void AlsaDumpPcmInfo(CtlSourceT *source, const char* info, snd_pcm_t *pcm snd_pcm_dump(pcm, out); snd_output_buffer_string(out, &buffer); - AFB_ApiNotice(source->api, "AlsaPCMDump: %s", buffer); + AFB_ApiNotice(mixer->api, "AlsaPCMDump: %s", buffer); snd_output_close(out); } -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 AlsaDumpElemConfig(SoftMixerT *mixer, const char* info, const char* elem) { + snd_config_update(); + snd_config_t *pcmConfig; + snd_config_search(snd_config, elem, &pcmConfig); + AlsaDumpCtlConfig(mixer, info, pcmConfig, 1); } -PUBLIC void AlsaDumpCtlConfig(CtlSourceT *source, const char* info, snd_config_t *config, int indent) { +PUBLIC void AlsaDumpCtlConfig(SoftMixerT *mixer, const char* info, snd_config_t *config, int indent) { snd_config_iterator_t it, next; // hugly hack to get minimalist indentation @@ -184,28 +198,28 @@ PUBLIC void AlsaDumpCtlConfig(CtlSourceT *source, const char* info, snd_config_t 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); + AFB_ApiNotice(mixer->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); + AFB_ApiNotice(mixer->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); + AFB_ApiNotice(mixer->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); + AFB_ApiNotice(mixer->api, "%s: %s %s { ", info, pretty, key); + AlsaDumpCtlConfig(mixer, info, node, indent + 2); + AFB_ApiNotice(mixer->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); + AFB_ApiNotice(mixer->api, "%s: %s: key=%s unknown=%s", info, pretty, key, valueS); break; } } |