aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFulup Ar Foll <fulup@iot.bzh>2018-05-13 22:07:22 +0200
committerFulup Ar Foll <fulup@iot.bzh>2018-05-13 22:07:22 +0200
commit6f13e28ba698a2b0145acbb926b79cd569a31f44 (patch)
tree061e2daace484aea73200ab85db39b3fafeb95e4
parent0eb15da6365910ba3f290e3254719fd412ae0155 (diff)
First testable version.
Mixing with volume and mute per audio role works.
-rw-r--r--README.md359
-rw-r--r--conf.d/cmake/config.cmake2
-rw-r--r--conf.d/project/lua.d/softmixer-02.lua168
-rw-r--r--conf.d/project/samples/softmixer-multi-01.lua (renamed from conf.d/project/lua.d/softmixer-01.lua)74
-rw-r--r--conf.d/project/sounds/trio-divi-alkazabach.mp3bin0 -> 7254516 bytes
-rw-r--r--plugins/alsa/alsa-api-sndcards.c118
-rw-r--r--plugins/alsa/alsa-api-sndloops.c80
-rw-r--r--plugins/alsa/alsa-api-sndstreams.c166
-rw-r--r--plugins/alsa/alsa-api-sndzones.c3
-rw-r--r--plugins/alsa/alsa-core-ctl.c336
-rw-r--r--plugins/alsa/alsa-core-pcm.c29
-rw-r--r--plugins/alsa/alsa-plug-dmix.c4
-rw-r--r--plugins/alsa/alsa-plug-multi.c4
-rw-r--r--plugins/alsa/alsa-plug-rate.c81
-rw-r--r--plugins/alsa/alsa-plug-route.c4
-rw-r--r--plugins/alsa/alsa-plug-stream.c19
-rw-r--r--plugins/alsa/alsa-softmixer.c82
-rw-r--r--plugins/alsa/alsa-softmixer.h27
-rw-r--r--plugins/alsa/alsa-utils-bypath.c1
19 files changed, 884 insertions, 673 deletions
diff --git a/README.md b/README.md
index 6239afd..2fbb6b7 100644
--- a/README.md
+++ b/README.md
@@ -6,344 +6,61 @@ Softmixer controller for 4A (AGL Advance Audio Architecture).
* Author: Fulup Ar Foll fulup@iot.bzh
* Date : April-2018
-## Functionalities:
- - Create an application dedicate controller from a JSON config file
- - Each controls (eg: navigation, multimedia, ...) is a suite of actions. When all actions succeed control is granted, if one fail control acces is denied.
- - Actions can either be:
- + Invocation to an other binding API, either internal or external (eg: a policy service, Alsa UCM, ...)
- + C routines from a user provider plugin (eg: policy routine, proprietary code, ...)
- + Lua script function. Lua provides access to every AGL appfw functionalities and can be extended from C user provided plugins.
-
-## Installation
- - Controler is a native part of AGL Advance Audio Framework but may be used independently with any other service or application binder.
- - Dependencies: the only dependencies are audio-common for JSON-WRAP and Filescan-utils capabilities.
- - Controler relies on Lua-5.3, when not needed Lua might be removed at compilation time.
-
-## Monitoring
- - Default test HTML page expect monitoring HTML page to be accessible from /monitoring for this to work you should
- * place monitoring HTML pages in a well known location eg: $HOME/opt/monitoring
- * start your binder with the alias option e.g. afb-daemon --port=1234 --alias=/monitoring:/home/fulup/opt/afb-monitoring --ldpaths=. --workdir=. --roothttp=../htdocs
-
-## Config
-
-Configuration is loaded dynamically during startup time. The controller scans CONTROL_CONFIG_PATH for a file corresponding to pattern
-"onload-bindername-xxxxx.json". When controller runs within AAAA binder it searches for "onload-audio-xxxx.json". First file found in the path the loaded
-any other files corresponding to the same pather are ignored and only generate a warning.
-
-Each bloc in the configuration file are defined with
- * label: must be provided is used either for debugging or as input for the action (eg: signal name, control name, ...)
- * info: optional used for documentation purpose only
-
-Note by default controller config search path is defined at compilation time, but path might be overloaded with CONTROL_CONFIG_PATH
-environment variable. Setenv 'CONTROL_ONLOAD_PROFILE'=xxxx to overload 'onload-default-profile' initialisation sequence.
-
-### Config is organised in 4 sections:
-
- * metadata
- * onload defines the set of action to be executed at startup time
- * control defines the set of controls with corresponding actions
- * event define the set of actions to be executed when receiving a given signal
-
-### Metadata
-
-As today matadata is only used for documentation purpose.
- * label + version mandatory
- * info optional
-
-### OnLoad section
-
-Defines startup time configuration. Onload may provide multiple initialisation profiles, each with a different label.
- * label is mandatory. Label is used to select onload profile at initialisation through DispatchOneOnLoad("onload-label") API;
- * info is optional
- * plugin provides optional unique plugin name. Plugin should follow "onload-bindername-xxxxx.ctlso" patern
- and are search into CONTROL_PLUGIN_PATH. When defined controller will execute user provided function context=CTLP_ONLOAD(label,version,info).
- The context returned by this routine is provided back to any C routines call later by the controller. Note that Lua2C function
- are prefix in Lua script with plugin label (eg: MyPlug: in following config sample)
- * lua2c list of Lua commands shipped with provided plugin.
- * require list of binding that should be initialised before the controller starts. Note that some listed requirer binding might be absent,
- nevertheless any present binding from this list will be started before controller binding, missing ones generate a warning.
- * action the list of action to execute during loadtime. Any failure in action will prevent controller binding from starting.
-
-### Control section
-
-Defines a list of controls that are accessible through (api="control", verb="request", control="control-label").
-
- * label mandatory
- * info optional
- * permissions Cynara needed privileges to request this control (same as AppFw-V2)
- * action the list of actions
-
-### Event section
-
-Defines a list of actions to be executed on event reception. Even can do anything a controller can (change state,
-send back signal, ...) eg: if a controller subscribes to vehicule speed, then speed-event may ajust master-volume to speed.
-
- * label mandatory
- * info optional
- * action the list of actions
-
-### Actions Categories
-
-Controler support tree categories of actions. Each action return a status status where 0=success and 1=failure.
- * AppFw API, Provides a generic model to request other bindings. Requested binding can be local (eg: ALSA/UCM) or
- external (eg: vehicle signalling).
- * api provides requested binding API name
- * verb provides verb to requested binding
- * args optionally provides a jsonc object for targeted binding API. Note that 'args' are statically defined
- in JSON configuration file. Controler client may also provided its own arguments from the query list. Targeted
- binding receives both arguments defined in the config file and the argument provided by controller client.
- * C-API, when defined in the onload section, the plugin may provided C native API with CTLP-CAPI(apiname, label, args, query, context).
- Plugin may also create Lua command with CTLP-Lua2C(LuaFuncName, label, args, query, context). Where args+query are JSONC object
- and context the value return from CTLP_ONLOAD function. Any missing value is set to NULL.
- * Lua-API, when compiled with Lua option, the controller support action defined directly in Lua script. During "onload" phase the
- controller search in CONTROL_Lua_PATH file with pattern "onload-bindername-xxxx.lua". Any file corresponding to this pattern
- is automatically loaded. Any function defined in those Lua script can be called through a controller action. Lua functions receive
- three parameters (label, args, query).
-
-Note: Lua added functions systematically prefix. AGL standard AppFw functions are prefixed with AGL: (eg: AGL:notice(), AGL_success(), ...).
-User Lua functions added though the plugin and CTLP_Lua2C are prefix with plugin label (eg: MyPlug:HelloWorld1).
-
-### Avaliable Application Framework Commands
-
-Each Lua AppFw commands should be prefixed by AFB:
-
- * AFB:notice ("format", arg1,... argn) LUA table are print directly as json string with '%s'.
- AFB:error, AFB:warning, AFB:info, AFB:debug work on the same model. Printed message are limited to 512 characters.
-
- * AFB:service ('API', 'VERB', {query}, "Lua_Callback_Name", {context}) asynchronous call to an other binding. When empty query/context should be set to '{}'
- and not to 'nil'. When 'nil' Lua does not send 'NULL' value but remove arguments to calling stack. WARNING:"Callback"
- is the name of the callback as a string and not a pointer to the callback. (If someone as a solution to fix this, please
- let me known). Callback is call as LUA "function Alsa_Get_Hal_CB (error, result, context)" where:
- * error is a Boolean
- * result is the full answer from AppFw (do not forget to extract response)
- * context is a copy of the Lua table pas as argument (warning it's a copy not a pointer to original table)
-
- * error,result=AFB:servsync('API', 'VERB', {query}) Save as previous but for synchronous call. Note that Lua accept multiple
- return. AFB:servsync return both the error message and the response as a Lua table. Like for AFB:service user should not
- forget to extract response from result.
-
- * AFB:success(request, response) request is the opaque handle pass when Lua is called from (api="control", verb="docall").
- Response is a Lua table that will be return to client.
-
- * AFB:fail(request, response) same as for success. Note that LUA generate automatically the error code from Lua function name.
- The response is tranformed to a json string before being return to client.
-
- * EventHandle=AFB:evtmake("MyEventName") Create an event and return the handle as an opaque handle. Note that due to a limitation
- of json_object this opaque handle cannot be passed as argument in a callback context.
-
- * AFB:subscribe(request, MyEventHandle) Subscribe a given client to previously created event.
-
- * AFB:evtpush (MyEventHandle, MyEventData) Push an event to every subscribed client. MyEventData is a Lua table that will be
- send as a json object to corresponding clients.
+## Compile
+```
+ mkdir build
+ cd build
+ cmake ..
+ make
+```
- * timerHandle=AFB:timerset (MyTimer, "Timer_Test_CB", context) Initialise a timer from MyTimer Lua table. This table should contend 3 elements:
- MyTimer={[l"abel"]="MyTimerName", ["delay"]=timeoutInMs, ["count"]=nBOfCycles}. Note that is count==0 then timer is cycle
- infinitively. Context is a standard Lua table. This function return an opaque handle to be use to further control the timer.
+## Install Alsa Loopback
- * AFB:timerclear(timerHandle) Kill an existing timer. Return an error when timer does not exit.
+```
+ sudo modprobe alsa-aloop
+```
- * MyTimer=AFB:timerget(timerHandle) Return Label, Delay and Count of an active timer. Return an error when timerHandle does not
- point on an active timer.
+## Assert LUA config file match your config
-Note: Except for function call during binding initialisation period. Lua call are protected and should return clean message
- even when improperly used. If you find bug please report.
+```
+ vim $PROJECT_ROOT/conf.d/project/lua.d/softmixer-01.lua
-### Adding Lua command from User Plugin
+ # make sure both your loopback and targeted sound card path are valid
+```
-User Plugin is optional and may provide either native C-action accessible directly from controller actions as defined in
-JSON config file, or alternatively may provide at set of Lua commands usable inside any script (onload, control,event). A simple
-plugin that provide both natice C API and Lua commands is provided as example (see ctl-plugin-sample.c). Technically a
-plugin is a simple sharelibrary and any code fitting in sharelib might be used as a plugin. Developer should nevertheless
-not forget that except when no-concurrency flag was at binding construction time, any binding should to be thread safe.
-A plugin must be declare with CTLP_REGISTER("MyCtlSamplePlugin"). This entry point defines a special structure that is check
-at plugin load time by the controller. Then you have an optional init routine declare with CTLP_ONLOAD(label, version, info).
-This init routine receives controller onload profile as selected by DispatchOnLoad("profile"). The init routine may create
-a plugin context that is later one presented to every plugin API this for both LUA and native C ones. Then each:
+## Run from shell
- * C API declare with CTLP_CAPI (MyCFunction, label, argsJ, queryJ, context) {your code}. Where:
- * MyFunction is your function
- * Label is a string containing the name of your function
- * ArgsJ a json_object containing the argument attach the this control in JSON config file.
- * context your C context as return from CTLP_ONLOAD
+```
+ afb-daemon --name 4a-softmixer-afbd --port=1234 --workdir=/home/fulup/Workspace/Audio-4a/4a-softmixer/build \
+ --binding=package/lib/softmixer-binding.so --roothttp=package/htdocs --token= --tracereq=common --verbose
+
+ # lua test script should return a response looking like
+ response= {
+ [1] = { ["uid"] = navigation,["runid"] = 101,["alsa"] = hw:5,0,6,["volid"] = 103,}
+ , ["params"] = { ["channels"] = 2,["format"] = 2,["rate"] = 48000,["access"] = 3,}
+ ,[2] = { ....
+ }
+
+ # runid: pause/resume alsa control you may change it with 'amixer -D hw:Loopback cset numid=101 on|off
+ # volid: volume alsa control you may change it from 'alsamixer -Dhw:Loppback' or with 'amixer -D hw:Loopback cset numid=103 NN (o-100%)
+```
- * Lua API declarewith TLP_LUA2C (MyLuaCFunction, label, argsJ, context) {your code}. Where
- * MyLuaCFunction is both the name of your C function and Lua command
- * Label your function name as a string
- * Args the arguments passed this time from Lua script and not from Json config file.
- * Query is not provided as LuaC function are called from a script and not directly from controller action list.
-Warning: Lua samples use with controller enforce strict mode. As a result every variables should be declare either as
-local or as global. Unfortunately "luac" is not smart enough to handle strict mode at build time and errors only appear
-at run time. Because of this strict mode every global variables (which include functions) should be prefix by '_'.
-Note that LUA require an initialisation value for every variables and declaring something like "local myvar" wont
-allocate "myvar"
-### Debugging Facilities
-Controler Lua script are check for syntax from CMAKE template with Luac. When needed to go further an developer API allow to
-execute directly Lua command within controller context from Rest/Ws (api=control, verb=lua_doscript). DoScript API takes two
-other optional arguments func=xxxx where xxxx is the function to execute within Lua script and args a JSON object to provide
-input parameter. When funcname is not given by default the controller try to execute middle filename doscript-xxxx-????.lua.
+Retrieve audio-stream alsa endpoint from response to 'L2C:snd_streams' command. Depending on your config 'hw:XXX' will change.
+Alsa snd-aloop impose '0' as playback device. Soft mixer will start from last subdevice and allocates one subdev for each audio-stream.
-When executed from controller Lua script may use any AppFw Apis as well as any L2C user defined commands in plugin.
-### Running as Standalone Controller
+## Play some music
-Controller is a standard binding and can then be started independently of AAAA. When started with from build repository with
+Current version does not handle audio rate conversion, using gstreamer or equivalent to match with audio hardware params is mandatory.
```
-afb-daemon --port=1234 --workdir=. --roothttp=../htdocs --tracereq=common --token= --verbose --binding=./Controller-afb/afb-control-afb.so
-```
-
-Afb-Daemon only load controller bindings without search for the other binding. In this case the name of the process is not change
-to afb-audio and controller binding will search for a configuration file name 'onload-daemon-xxx.json'. This model can be used
-to implement for testing purpose or simply to act as the glue in between a UI and other binder/services.
-
-## Config Sample
+ gst123 --audio-output alsa=hw:XXX,0,7 $PROJECT_ROOT/conf.d/project/sounds/trio-divi-alkazabach.wav
-Here after a simple configuration sample.
-
-```
-{
- "$schema": "ToBeDone",
- "metadata": {
- "label": "sample-audio-control",
- "info": "Provide Default Audio Policy for Multimedia, Navigation and Emergency",
- "version": "1.0"
- },
- "onload": [{
- "label": "onload-default",
- "info": "onload initialisation config",
- "plugin": {
- "label" : "MyPlug",
- "sharelib": "ctl-audio-plugin-sample.ctlso",
- "lua2c": ["Lua2cHelloWorld1", "Lua2cHelloWorld2"]
- },
- "require": ["intel-hda", "jabra-usb", "scarlett-usb"],
- "actions": [
- {
- "label": "onload-sample-cb",
- "info": "Call control sharelib install entrypoint",
- "callback": "SamplePolicyInit",
- "args": {
- "arg1": "first_arg",
- "nextarg": "second arg value"
- }
- }, {
- "label": "onload-sample-api",
- "info": "Assert AlsaCore Presence",
- "api": "alsacore",
- "verb": "ping",
- "args": "test"
- }, {
- "label": "onload-hal-lua",
- "info": "Load avaliable HALs",
- "lua": "Audio_Init_Hal"
- }
- ]
- }],
- "controls":
- [
- {
- "label": "multimedia",
- "permissions": "urn:AGL:permission:audio:public:mutimedia",
- "actions": {
- "label": "multimedia-control-lua",
- "info": "Call Lua Script function Test_Lua_Engin",
- "lua": "Audio_Set_Multimedia"
- }
- }, {
- "label": "navigation",
- "permissions": "urn:AGL:permission:audio:public:navigation",
- "actions": {
- "label": "navigation-control-lua",
- "info": "Call Lua Script to set Navigation",
- "lua": "Audio_Set_Navigation"
- }
- }, {
- "label": "emergency",
- "permissions": "urn:AGL:permission:audio:public:emergency",
- "actions": {
- "label": "emergency-control-ucm",
- "lua": "Audio_Set_Emergency"
- }
- }, {
- "label": "multi-step-sample",
- "info" : "all actions must succeed for control to be accepted",
- "actions": [{
- "label": "multimedia-control-cb",
- "info": "Call Sharelib Sample Callback",
- "callback": "sampleControlNavigation",
- "args": {
- "arg1": "snoopy",
- "arg2": "toto"
- }
- }, {
- "label": "navigation-control-ucm",
- "api": "alsacore",
- "verb": "ping",
- "args": {
- "test": "navigation"
- }
- }, {
- "label": "navigation-control-lua",
- "info": "Call Lua Script to set Navigation",
- "lua": "Audio_Set_Navigation"
- }]
- }
- ],
- "events":
- [
- {
- "label": "Vehicle-Speed",
- "info": "Action when Vehicule speed change",
- "actions": [
- {
- "label": "speed-action-1",
- "callback": "Blink-when-over-130",
- "args": {
- "speed": 130
- "blink-speed": 1000
- }
- }, {
- "label": "Adjust-Volume",
- "lua": "Adjust_Volume_To_Speed",
- }
- ]
- },
- {
- "label": "Reverse-Engage",
- "info": "When Reverse Gear is Engage",
- "actions": [
- {
- "label": "Display-Rear-Camera",
- "callback": "Display-Rear-Camera",
- }, {
- "label": "Prevent-Phone-Call",
- "api" : "phone",
- "verb" : "status",
- "args": {
- "call-accepted": false
- }
- }
- ]
- },
- {
- "label": "Neutral-Engage",
- "info": "When Reverse Neutral is Engage",
- "actions": [
- {
- "label": "Authorize-Video",
- "api" : "video",
- "verb" : "status",
- "args": {
- "tv-accepted": true
- }
- }
- ]
- }
- ]
-}
+ gst123 --audio-output alsa=hw:XXX,0,??? other sound file
+ speaker-test -D hw:XXX:0:??? -twav -c!! 'cc' is the number of channel and depends on the audio stream zone target.
```
diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake
index b807a5e..1b5092c 100644
--- a/conf.d/cmake/config.cmake
+++ b/conf.d/cmake/config.cmake
@@ -198,7 +198,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} --binding=package/lib/softmixer-binding.so --ldpath=${CMAKE_INSTALL_PREFIX}/4a-alsa-core/lib --roothttp=package/htdocs --token=\"${AFB_TOKEN}\" --tracereq=common --verbose")
+set(CLOSING_MESSAGE "Typical binding launch: afb-daemon --name ${PROJECT_NAME}-afbd --port=${AFB_REMPORT} --workdir=${CMAKE_BINARY_DIR} --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/lua.d/softmixer-02.lua b/conf.d/project/lua.d/softmixer-02.lua
new file mode 100644
index 0000000..5ea52de
--- /dev/null
+++ b/conf.d/project/lua.d/softmixer-02.lua
@@ -0,0 +1,168 @@
+--[[
+ Copyright (C) 2016 "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.
+
+
+ NOTE: strict mode: every global variables should be prefixed by '_'
+--]]
+
+-- 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))
+
+end
+
+
+-- Display receive arguments and echo them to caller
+function _mixer_config_ (source, args)
+ do
+ local error
+ local response
+
+ -- ==================== Default rate ===========================
+
+ local audio_defaults = {
+ ["rate"] = 48000,
+ }
+
+ -- ======================= Loop PCM ===========================
+
+ local snd_aloop = {
+ ["uid"] = "Alsa-Loop",
+ ["devpath"] = "/dev/snd/by-path/platform-snd_aloop.0",
+ ["devices"] = {["playback"]=0,["capture"]=1},
+ ["params"] = audio_defaults,
+ ["subdevs"] = {
+ {["subdev"]= 0, ["numid"]= 51},
+ {["subdev"]= 1, ["numid"]= 57},
+ {["subdev"]= 2, ["numid"]= 63},
+ {["subdev"]= 3, ["numid"]= 69},
+ {["subdev"]= 4, ["numid"]= 75},
+ {["subdev"]= 5, ["numid"]= 81},
+ {["subdev"]= 6, ["numid"]= 87},
+ {["subdev"]= 7, ["numid"]= 93},
+ }
+ }
+
+ error,response= L2C:snd_loops (source, snd_aloop)
+ if (error ~= 0) then
+ AFB:error (source, "--InLua-- L2C:snd_loops fail to attach sndcards=%s", Dump_Table(aloop))
+ goto OnErrorExit
+ else
+ AFB:notice (source, "--InLua-- L2C:snd_loops done response=%s\n", Dump_Table(response))
+ end
+
+
+ -- ============================= Sound Cards ===================
+
+ local sndcard_0 = {
+ ["uid"]= "YAMAHA-APU70",
+ ["devpath"]= "/dev/snd/by-id/usb-YAMAHA_Corporation_YAMAHA_AP-U70_USB_Audio_00-00",
+ ["params"] = snd_params,
+ ["sink"] = {
+ [0]= {["uid"]= "front-right", ["port"]= 0},
+ [1]= {["uid"]= "front-left", ["port"]= 1},
+ }
+ }
+
+ -- group sound card as one multi channels card
+ local sndcards= {
+ sndcard_0,
+ }
+
+ error,response= L2C:snd_cards (source, sndcards)
+ if (error ~= 0) then
+ AFB:error (source, "--InLua-- L2C:snd_cards fail to attach sndcards=%s", Dump_Table(sndcards))
+ goto OnErrorExit
+ else
+ AFB:notice (source, "--InLua-- L2C:snd_cards done response=%s\n", Dump_Table(response))
+ end
+
+ -- ============================= Zones ===================
+
+ local zone_front= {
+ ["uid"] = "front-seats",
+ ["type"] = "playback",
+ ["mapping"] = {
+ {["target"]="front-right",["channel"]=0},
+ {["target"]="front-left" ,["channel"]=1},
+ }
+ }
+
+ local multi_zones = {
+ zone_front,
+ }
+
+ error,response= L2C:snd_zones (source, multi_zones)
+ if (error ~= 0) then
+ AFB:error (source, "--InLua-- L2C:snd_zones fail to attach sndcards=%s", Dump_Table(multi_zones))
+ goto OnErrorExit
+ else
+ AFB:notice (source, "--InLua-- L2C:snd_zones done response=%s\n", Dump_Table(response))
+ end
+
+ -- =================== Audio Stream ============================
+
+ local stream_music= {
+ ["uid"] = "multimedia",
+ ["zone"] = "front-seats",
+ ["volume"]= 70,
+ ["mute"] = false,
+ }
+
+ local stream_navigation= {
+ ["uid"] = "navigation",
+ ["zone"] = "front-seats",
+ ["volume"]= 80,
+ ["mute"] = false,
+ }
+
+ local snd_streams = {
+ stream_music,
+ stream_navigation,
+ }
+
+ error,response= L2C:snd_streams (source, snd_streams)
+ if (error ~= 0) then
+ AFB:error (source, "--InLua-- L2C:snd_streams fail to attach sndcards=%s", Dump_Table(aloop))
+ goto OnErrorExit
+ else
+ AFB:notice (source, "--InLua-- L2C:streams_loops done response=%s\n", Dump_Table(response))
+ end
+
+
+ -- ================== Happy End =============================
+ AFB:notice (source, "--InLua-- _mixer_config_ done")
+ return 0 end
+
+ -- ================= Unhappy End ============================
+ ::OnErrorExit::
+ AFB:error (source, "--InLua-- snd_attach fail")
+ return 1 -- unhappy end --
+end
+
+-- Display receive arguments and echo them to caller
+function _init_softmixer_ (source, args)
+
+ -- create event to push change audio roles to potential listeners
+ _EventHandle=AFB:evtmake(source, "control")
+
+ _mixer_config_ (source, args)
+
+end
diff --git a/conf.d/project/lua.d/softmixer-01.lua b/conf.d/project/samples/softmixer-multi-01.lua
index ed60192..1b8ba02 100644
--- a/conf.d/project/lua.d/softmixer-01.lua
+++ b/conf.d/project/samples/softmixer-multi-01.lua
@@ -35,17 +35,45 @@ function _mixer_config_ (source, args)
local error
local response
+
+ -- ======================= Loop PCM ===========================
+
+ local snd_aloop = {
+ ["uid"] = "Alsa-Loop",
+ ["devpath"] = "/dev/snd/by-path/platform-snd_aloop.0",
+ ["devices"] = {["playback"]=0,["capture"]=1},
+ ["subdevs"] = {
+ {["subdev"]= 0, ["numid"]= 51},
+ {["subdev"]= 1, ["numid"]= 57},
+ {["subdev"]= 2, ["numid"]= 63},
+ {["subdev"]= 3, ["numid"]= 69},
+ {["subdev"]= 4, ["numid"]= 75},
+ {["subdev"]= 5, ["numid"]= 81},
+ {["subdev"]= 6, ["numid"]= 87},
+ {["subdev"]= 7, ["numid"]= 93},
+ }
+ }
+
+ error,response= L2C:snd_loops (source, snd_aloop)
+ if (error ~= 0) then
+ AFB:error (source, "--InLua-- L2C:snd_loops fail to attach sndcards=%s", Dump_Table(aloop))
+ goto OnErrorExit
+ else
+ AFB:notice (source, "--InLua-- L2C:snd_loops done response=%s\n", Dump_Table(response))
+ end
+
+
-- ============================= Sound Cards ===================
local snd_params = {
["rate"] = 48000,
- ["channel"]= 2,
+ ["channels"]= 2,
}
local sndcard_0 = {
["uid"]= "YAMAHA-APU70",
["devpath"]= "/dev/snd/by-id/usb-YAMAHA_Corporation_YAMAHA_AP-U70_USB_Audio_00-00",
- ["params"] = params,
+ ["params"] = snd_params,
["sink"] = {
[0]= {["uid"]= "front-right", ["port"]= 0},
[1]= {["uid"]= "front-left", ["port"]= 1},
@@ -55,7 +83,7 @@ function _mixer_config_ (source, args)
local sndcard_1 = {
["uid"]= "Jabra-Solemate",
["devpath"]= "/dev/snd/by-id/usb-0b0e_Jabra_SOLEMATE_v1.34.0-00",
- ["params"] = params,
+ ["params"] = snd_params,
["sink"] = {
[0]= {["uid"]= "front-right", ["port"]= 0},
[1]= {["uid"]= "front-left", ["port"]= 1},
@@ -64,7 +92,7 @@ function _mixer_config_ (source, args)
local sndcard_2 = {
["uid"]= "Jabra-410",
- ["params"] = params,
+ ["params"] = snd_params,
["devpath"]= "/dev/snd/by-id/usb-0b0e_Jabra_SPEAK_410_USB_745C4B15BD11x010900-00",
["sink"] = {
[0]= {["uid"]= "back-right", ["port"]= 0},
@@ -131,54 +159,32 @@ function _mixer_config_ (source, args)
AFB:notice (source, "--InLua-- L2C:snd_zones done response=%s\n", Dump_Table(response))
end
- -- ======================= Loop PCM ===========================
-
- local snd_aloop = {
- ["uid"] = "Alsa-Loop",
- ["devpath"] = "/dev/snd/by-path/platform-snd_aloop.0",
- ["devices"] = {["playback"]=0,["capture"]=1},
- ["subdevs"] = {
- {["subdev"]= 0, ["numid"]= 51},
- {["subdev"]= 1, ["numid"]= 57},
- {["subdev"]= 2, ["numid"]= 63},
- {["subdev"]= 3, ["numid"]= 69},
- {["subdev"]= 4, ["numid"]= 75},
- {["subdev"]= 5, ["numid"]= 81},
- {["subdev"]= 6, ["numid"]= 87},
- {["subdev"]= 7, ["numid"]= 93},
- }
- }
-
- error,response= L2C:snd_loops (source, snd_aloop)
- if (error ~= 0) then
- AFB:error (source, "--InLua-- L2C:snd_loops fail to attach sndcards=%s", Dump_Table(aloop))
- goto OnErrorExit
- else
- AFB:notice (source, "--InLua-- L2C:snd_loops done response=%s\n", Dump_Table(response))
- end
-
-- =================== Audio Stream ============================
local stream_music= {
["uid"] = "multimedia",
["zone"] = "all-seats",
["volume"]= 70,
- ["rate"] = 48000,
- ["mute"] = false;
+ ["params"] = {
+ ["rate"] = 44100,
+ ["rate"] = 48000,
+ ["format"]= "S16_LE",
+ },
+ ["mute"] = false,
}
local stream_navigation= {
["uid"] = "navigation",
["zone"] = "front-seats",
["volume"]= 80,
- ["mute"] = false;
+ ["mute"] = false,
}
local stream_children= {
["uid"] = "children",
["zone"] = "back-seats",
["volume"]= 50,
- ["mute"] = false;
+ ["mute"] = false,
}
local snd_streams = {
diff --git a/conf.d/project/sounds/trio-divi-alkazabach.mp3 b/conf.d/project/sounds/trio-divi-alkazabach.mp3
new file mode 100644
index 0000000..69ec868
--- /dev/null
+++ b/conf.d/project/sounds/trio-divi-alkazabach.mp3
Binary files differ
diff --git a/plugins/alsa/alsa-api-sndcards.c b/plugins/alsa/alsa-api-sndcards.c
index e98a294..daa996a 100644
--- a/plugins/alsa/alsa-api-sndcards.c
+++ b/plugins/alsa/alsa-api-sndcards.c
@@ -25,77 +25,121 @@ extern Lua2cWrapperT Lua2cWrap;
STATIC int ProcessOneChannel(CtlSourceT *source, const char* uid, json_object *channelJ, AlsaPcmChannels *channel) {
const char*channelUid;
-
+
int error = wrap_json_unpack(channelJ, "{ss,si !}", "uid", &channelUid, "port", &channel->port);
if (error) goto OnErrorExit;
- channel->uid=strdup(channelUid);
+ 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));
+ AFB_ApiError(source->api, "ProcessOneChannel: sndcard=%s channel: missing (uid||port) json=%s", uid, json_object_get_string(channelJ));
return -1;
}
-STATIC int ProcessSndParams(CtlSourceT *source, const char* uid, json_object *paramsJ, AlsaPcmHwInfoT *params) {
-
- int error = wrap_json_unpack(paramsJ, "{s?i,s?i !}", "rate", &params->rate, "channels", &params->channels);
+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",&params->rate,"channels", &params->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:
- AFB_ApiError(source->api, "ProcessSndParams: sndcard=%s params: missing (rate|channel) params=%s", uid, json_object_get_string(paramsJ));
return -1;
}
STATIC int ProcessOneSndCard(CtlSourceT *source, json_object *sndcardJ, AlsaPcmInfoT *snd) {
- json_object *sinkJ, *paramsJ=NULL;
+ 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",&paramsJ);
+ error = wrap_json_unpack(sndcardJ, "{ss,s?s,s?s,s?i,s?i,s?i,so,s?o !}", "uid", &snd->uid, "devpath", &snd->devpath, "cardid", &snd->cardid
+ , "cardidx", &snd->cardidx, "device", &snd->device, "subdev", &snd->subdev, "sink", &sinkJ, "params", &paramsJ);
if (error || !snd->uid || !sinkJ || (!snd->devpath && !snd->cardid && snd->cardidx)) {
AFB_ApiNotice(source->api, "ProcessOneSndCard missing 'uid|path|cardid|cardidx|channels|device|subdev|numid|params' devin=%s", json_object_get_string(sndcardJ));
goto OnErrorExit;
}
-
- if (paramsJ) error= ProcessSndParams(source, snd->uid, paramsJ, &snd->params);
- if (error) {
- AFB_ApiError(source->api, "ProcessOneSndCard: sndcard=%s invalid params=%s", snd->uid, json_object_get_string(paramsJ));
- goto OnErrorExit;
+
+ 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);
+ 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;
+ goto OnErrorExit;
} else {
- snd_pcm_close(dmixPcm->handle);
- snd->cardid=dmixPcm->cardid;
+ snd->cardid = dmixPcm->cardid;
}
switch (json_object_get_type(sinkJ)) {
case json_type_object:
- snd->ccount=1;
- snd->channels = calloc(snd->ccount+1, sizeof (AlsaPcmChannels));
+ snd->ccount = 1;
+ snd->channels = calloc(snd->ccount + 1, sizeof (AlsaPcmChannels));
error = ProcessOneChannel(source, snd->uid, sndcardJ, &snd->channels[0]);
if (error) goto OnErrorExit;
break;
case json_type_array:
- snd->ccount = (int)json_object_array_length(sinkJ);
- snd->channels = calloc(snd->ccount+1, sizeof (AlsaPcmChannels));
+ snd->ccount = (int) json_object_array_length(sinkJ);
+ snd->channels = calloc(snd->ccount + 1, sizeof (AlsaPcmChannels));
for (int idx = 0; idx < snd->ccount; idx++) {
json_object *channelJ = json_object_array_get_idx(sinkJ, idx);
error = ProcessOneChannel(source, snd->uid, channelJ, &snd->channels[idx]);
@@ -115,13 +159,13 @@ OnErrorExit:
CTLP_LUA2C(snd_cards, source, argsJ, responseJ) {
AlsaPcmInfoT *sndcards;
-
+
int error;
size_t count;
switch (json_object_get_type(argsJ)) {
case json_type_object:
- count= 1;
+ count = 1;
sndcards = calloc(count + 1, sizeof (AlsaPcmInfoT));
error = ProcessOneSndCard(source, argsJ, &sndcards[0]);
if (error) goto OnErrorExit;
@@ -141,23 +185,21 @@ CTLP_LUA2C(snd_cards, source, argsJ, responseJ) {
}
// register Sound card and multi when needed
- Softmixer->sndcardCtl= sndcards;
-
+ Softmixer->sndcardCtl = sndcards;
+
if (count == 1) {
-
+
// only one sound card we multi would be useless
Softmixer->multiPcm = &sndcards[0];
-
+
} else {
AlsaPcmInfoT *pcmMulti;
-
+
// instantiate an alsa multi plugin
- pcmMulti = AlsaCreateMulti(source, "PcmMulti");
+ pcmMulti = AlsaCreateMulti(source, "PcmMulti", 0);
if (!pcmMulti) goto OnErrorExit;
- // Close Multi and save into globak handle for further use
- snd_pcm_close(pcmMulti->handle);
- Softmixer->multiPcm= pcmMulti;
+ Softmixer->multiPcm = pcmMulti;
}
return 0;
diff --git a/plugins/alsa/alsa-api-sndloops.c b/plugins/alsa/alsa-api-sndloops.c
index 5599ae0..be12c11 100644
--- a/plugins/alsa/alsa-api-sndloops.c
+++ b/plugins/alsa/alsa-api-sndloops.c
@@ -24,23 +24,34 @@
// Fulup need to be cleanup with new controller version
extern Lua2cWrapperT Lua2cWrap;
-STATIC int ProcessOneSubdev(CtlSourceT *source, AlsaSndLoopT *loop, json_object *subdevJ, AlsaPcmInfoT *subdev) {
-
- int error = wrap_json_unpack(subdevJ, "{si,si !}", "subdev", &subdev->subdev, "numid", &subdev->numid);
+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", &paramsJ);
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);
+ 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->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->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) {
@@ -55,27 +66,43 @@ OnErrorExit:
}
STATIC int ProcessOneLoop(CtlSourceT *source, json_object *loopJ, AlsaSndLoopT *loop) {
- json_object *subdevsJ=NULL, *devicesJ=NULL;
+ json_object *subdevsJ = NULL, *devicesJ = NULL, *paramsJ = NULL;
int error;
- error = wrap_json_unpack(loopJ, "{ss,s?s,s?s,s?i,s?o,so}", "uid",&loop->uid, "devpath",&loop->devpath, "cardid",&loop->cardid
- , "cardidx",&loop->cardidx, "devices",&devicesJ, "subdevs",&subdevsJ);
+ error = wrap_json_unpack(loopJ, "{ss,s?s,s?s,s?i,s?o,so,s?o !}", "uid", &loop->uid, "devpath", &loop->devpath, "cardid", &loop->cardid
+ , "cardidx", &loop->cardidx, "devices", &devicesJ, "subdevs", &subdevsJ, "params", &paramsJ);
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;
+ }
+
// make sure useful information will not be removed
- loop->uid=strdup(loop->uid);
- if (loop->cardid) loop->cardid=strdup(loop->cardid);
- if (loop->devpath) loop->cardid=strdup(loop->devpath);
-
+ loop->uid = strdup(loop->uid);
+ if (loop->cardid) loop->cardid = strdup(loop->cardid);
+ if (loop->devpath) loop->cardid = strdup(loop->devpath);
+
// Default devices is payback=0 capture=1
if (!devicesJ) {
- loop->playback=0;
- loop->capture=1;
+ loop->playback = 0;
+ loop->capture = 1;
} else {
- error = wrap_json_unpack(devicesJ, "{si,si}", "capture",&loop->capture, "playback", &loop->playback);
+ 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;
@@ -85,24 +112,24 @@ STATIC int ProcessOneLoop(CtlSourceT *source, json_object *loopJ, AlsaSndLoopT *
switch (json_object_get_type(subdevsJ)) {
case json_type_object:
loop->scount = 1;
- loop->subdevs = calloc(loop->scount+1, sizeof (AlsaPcmInfoT));
- error = ProcessOneSubdev(source, loop, subdevsJ, &loop->subdevs[0]);
+ 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));
+ loop->scount = (int) json_object_array_length(subdevsJ);
+ loop->subdevs = calloc(loop->scount + 1, sizeof (AlsaPcmInfoT));
for (int idx = 0; idx < loop->scount; idx++) {
json_object *subdevJ = json_object_array_get_idx(subdevsJ, idx);
- error = ProcessOneSubdev(source, loop, subdevJ, &loop->subdevs[idx]);
+ error = ProcessOneSubdev(source, loop, subdevJ, loopDefParams, &loop->subdevs[idx]);
if (error) goto OnErrorExit;
}
break;
default:
- AFB_ApiError(source->api, "L2C:ProcessOneLoop=%s invalid subdevs= %s", loop->uid, json_object_get_string(subdevsJ));
+ AFB_ApiError(source->api, "L2C:ProcessOneLoop=%s invalid subdevs= %s", loop->uid, json_object_get_string(subdevsJ));
goto OnErrorExit;
}
-
+
return 0;
OnErrorExit:
@@ -111,7 +138,7 @@ OnErrorExit:
CTLP_LUA2C(snd_loops, source, argsJ, responseJ) {
int error;
- AlsaSndLoopT *sndLoop = calloc (1, sizeof(AlsaSndLoopT));
+ AlsaSndLoopT *sndLoop = calloc(1, sizeof (AlsaSndLoopT));
if (json_object_get_type(argsJ) != json_type_object) {
AFB_ApiError(source->api, "L2C:sndloops: invalid object type= %s", json_object_get_string(argsJ));
@@ -127,7 +154,6 @@ CTLP_LUA2C(snd_loops, source, argsJ, responseJ) {
// register routed into global softmixer handle
Softmixer->loopCtl = sndLoop;
-
return 0;
OnErrorExit:
diff --git a/plugins/alsa/alsa-api-sndstreams.c b/plugins/alsa/alsa-api-sndstreams.c
index 1ee0029..d02d47e 100644
--- a/plugins/alsa/alsa-api-sndstreams.c
+++ b/plugins/alsa/alsa-api-sndstreams.c
@@ -26,39 +26,32 @@ extern Lua2cWrapperT Lua2cWrap;
STATIC int ProcessOneStream(CtlSourceT *source, json_object *streamJ, AlsaSndStreamT *stream) {
int error;
- const char*format=NULL;
+ json_object *paramsJ = NULL;
- error = wrap_json_unpack(streamJ, "{ss,ss,s?i,s?b,s?i,s?i,s?i !}", "uid", &stream->uid, "zone", &stream->zone, "volume", &stream->volume, "mute", stream->mute
- , "rate", &stream->params.rate, "format", &format, "access", &stream->params.access);
+ // Make sure default runs
+ stream->volume = ALSA_DEFAULT_PCM_VOLUME;
+ stream->mute = 0;
+
+ error = wrap_json_unpack(streamJ, "{ss,ss,s?i,s?b,s?o !}", "uid", &stream->uid, "zone", &stream->zone, "volume", &stream->volume
+ , "mute", stream->mute, "params", &paramsJ);
+ if (error) {
+ AFB_ApiNotice(source->api, "ProcessOneStream missing 'uid|zone|volume|rate|mute|params' stream=%s", json_object_get_string(streamJ));
+ goto OnErrorExit;
+ }
+
+ if (paramsJ) error = ProcessSndParams(source, stream->uid, paramsJ, &stream->params);
if (error) {
- AFB_ApiNotice(source->api, "ProcessOneStream missing 'uid|zone|volume|rate|mute' stream=%s", json_object_get_string(streamJ));
+ AFB_ApiError(source->api, "ProcessOneSndCard: sndcard=%s invalid params=%s", 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;
}
- if (!format) stream->params.format=SND_PCM_FORMAT_UNKNOWN;
- else if (!strcasecmp (format, "S16_LE")) stream->params.format=SND_PCM_FORMAT_S16_LE;
- else if (!strcasecmp (format, "S16_BE")) stream->params.format=SND_PCM_FORMAT_S16_BE;
- else if (!strcasecmp (format, "U16_LE")) stream->params.format=SND_PCM_FORMAT_U16_LE;
- else if (!strcasecmp (format, "U16_BE")) stream->params.format=SND_PCM_FORMAT_U16_BE;
- else if (!strcasecmp (format, "S32_LE")) stream->params.format=SND_PCM_FORMAT_S32_LE;
- else if (!strcasecmp (format, "S32_BE")) stream->params.format=SND_PCM_FORMAT_S32_BE;
- else if (!strcasecmp (format, "U32_LE")) stream->params.format=SND_PCM_FORMAT_U32_LE;
- else if (!strcasecmp (format, "U32_BE")) stream->params.format=SND_PCM_FORMAT_U32_BE;
- else if (!strcasecmp (format, "S24_LE")) stream->params.format=SND_PCM_FORMAT_S24_LE;
- else if (!strcasecmp (format, "S24_BE")) stream->params.format=SND_PCM_FORMAT_S24_BE;
- else if (!strcasecmp (format, "U24_LE")) stream->params.format=SND_PCM_FORMAT_U24_LE;
- else if (!strcasecmp (format, "U24_BE")) stream->params.format=SND_PCM_FORMAT_U24_BE;
- else if (!strcasecmp (format, "S8")) stream->params.format=SND_PCM_FORMAT_S8;
- else if (!strcasecmp (format, "U8")) stream->params.format=SND_PCM_FORMAT_U8;
- else if (!strcasecmp (format, "FLOAT_LE")) stream->params.format=SND_PCM_FORMAT_FLOAT_LE;
- else if (!strcasecmp (format, "FLOAT_BE")) stream->params.format=SND_PCM_FORMAT_FLOAT_LE;
- else {
- AFB_ApiNotice(source->api, "ProcessOneStream unsupported format 'uid|zone|volume|rate|mute' stream=%s", json_object_get_string(streamJ));
- goto OnErrorExit;
- }
-
- if(!stream->params.rate) stream->params.rate=ALSA_DEFAULT_PCM_RATE;
-
// make sure remain valid even when json object is removed
stream->uid = strdup(stream->uid);
stream->zone = strdup(stream->zone);
@@ -72,6 +65,7 @@ OnErrorExit:
CTLP_LUA2C(snd_streams, source, argsJ, responseJ) {
AlsaSndStreamT *sndStream;
int error;
+ long value;
size_t count;
// assert static/global softmixer handle get requited info
@@ -111,13 +105,13 @@ CTLP_LUA2C(snd_streams, source, argsJ, responseJ) {
// return stream data to application as a json array
- *responseJ = json_object_new_array();
-
+ *responseJ = json_object_new_array();
+
for (int idx = 0; sndStream[idx].uid != NULL; idx++) {
- json_object *streamJ;
-
+ json_object *streamJ, *paramsJ;
+
// Search for a free loop capture device
- AFB_ApiNotice(source->api, "L2C:sndstreams stream=%s Start", (char*)sndStream[idx].uid);
+ AFB_ApiNotice(source->api, "L2C:sndstreams stream=%s Start", (char*) sndStream[idx].uid);
ctlLoop->scount--;
if (ctlLoop->scount < 0) {
AFB_ApiError(source->api, "L2C:sndstreams no more subdev avaliable in loopback=%s", ctlLoop->uid);
@@ -126,43 +120,107 @@ CTLP_LUA2C(snd_streams, source, argsJ, responseJ) {
// 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);
+ playbackDev->device = ctlLoop->capture;
+ AlsaPcmInfoT *captureDev = AlsaByPathOpenPcm(source, playbackDev, SND_PCM_STREAM_CAPTURE);
if (!captureDev) goto OnErrorExit;
-
- AlsaPcmInfoT *streamPcm = AlsaCreateStream(source, &sndStream[idx], captureDev);
+
+ // configure with default loopback subdev params
+ error = AlsaPcmConf(source, captureDev, &playbackDev->params);
+ if (error) goto OnErrorExit;
+
+ // Register capture PCM for active/pause event
+ if (captureDev->numid) {
+ error = AlsaCtlRegister(source, captureDev, 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, "AlsaCtlRegister [pcm=%s] fail attache sndcard", captureDev->cardid);
+ goto OnErrorExit;
+ }
+
+ // create mute control and register it as pause/resume ctl)
+ char runName[ALSA_CARDID_MAX_LEN];
+ snprintf(runName, sizeof (runName), "run-%s", sndStream[idx].uid);
+
+ // create a single boolean value control for pause/resume
+ int runNumid = AlsaCtlCreateControl(source, ctlDev, playbackDev, runName, 1, 0, 1, 1, !sndStream[idx].mute);
+ if (runNumid <= 0) goto OnErrorExit;
+
+ // register mute/unmute as a pause/resume control
+ error = AlsaCtlRegister(source, captureDev, runNumid);
+ 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", sndStream[idx].uid);
+ AlsaPcmInfoT *streamPcm = AlsaCreateStream(source, &sndStream[idx], captureDev, volName, 0);
if (!streamPcm) {
- AFB_ApiError(source->api, "L2C:sndstreams fail to create stream=%s", (char*) sndStream[idx].uid);
+ AFB_ApiError(source->api, "L2C:sndstreams:%s(pcm) fail to create stream", sndStream[idx].uid);
+ goto OnErrorExit;
+ }
+
+ // create volume control before softvol pcm is opened
+ int volNumid = AlsaCtlCreateControl(source, ctlDev, playbackDev, volName, streamPcm->params.channels, 0, 100, 1, sndStream[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", sndStream[idx].uid);
+ // AlsaPcmInfoT *ratePcm= AlsaCreateRate(source, rateName, streamPcm, 1);
+ // if (!ratePcm) {
+ // AFB_ApiError(source->api, "L2C:sndstreams:%s(pcm) fail to create rate converter", sndStream[idx].uid);
+ // goto OnErrorExit;
+ // }
+
+ // everything is not ready to open capture pcm
+ error = snd_pcm_open(&streamPcm->handle, sndStream[idx].uid, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+ if (error) {
+ AFB_ApiError(source->api, "L2C:sndstreams:%s(pcm) fail to open capture", sndStream[idx].uid);
goto OnErrorExit;
}
-
+
// capture stream inherit channel from targeted zone
captureDev->ccount = streamPcm->ccount;
- sndStream[idx].params.channels=streamPcm->ccount;
-
- // start stream pcm copy
- error = AlsaPcmCopy(source, captureDev, streamPcm, &sndStream[idx].params);
+ streamPcm->params.channels = streamPcm->ccount;
+
+ // start stream pcm copy (at this both capture & sink pcm should be open)
+ error = AlsaPcmCopy(source, captureDev, streamPcm, &streamPcm->params);
if (error) goto OnErrorExit;
- // Registration to event should be done after pcm_start
- if (captureDev->numid) {
- error = AlsaCtlRegister(source, captureDev, captureDev->numid);
- 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_ApiError(source->api, "L2C:sndstreams [capture=%s] fail to pause error=%s", captureDev->cardid, snd_strerror(error));
+ goto OnErrorExit;
}
-
+
// prepare response for application
- playbackDev->device= ctlLoop->playback;
+ playbackDev->device = ctlLoop->playback;
error = AlsaByPathDevid(source, playbackDev);
- wrap_json_pack(&streamJ, "{ss ss si}", "uid", sndStream[idx].uid, "alsa", playbackDev->cardid, "numid", captureDev->numid);
- json_object_array_add(*responseJ,streamJ);
-
+
+ error += wrap_json_pack(&paramsJ, "{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", runNumid, "params", paramsJ);
+ error += json_object_array_add(*responseJ, streamJ);
+ if (error) {
+ AFB_ApiError(source->api, "L2C:sndstreams:%s(stream) fail to prepare response", captureDev->cardid);
+ goto OnErrorExit;
+ }
+
+ snd_ctl_close(ctlDev);
// Debug Alsa Config
//AlsaDumpElemConfig (source, "\n\nAlsa_Config\n------------\n", "pcm");
//AlsaDumpPcmInfo(source, "\n\nPcm_config\n-----------\n", streamPcm->handle);
- AFB_ApiNotice(source->api, "L2C:sndstreams stream=%s OK\n", (char*) sndStream[idx].uid);
+ AFB_ApiNotice(source->api, "L2C:sndstreams:%s(stream) OK reponse=%s\n", streamPcm->uid, json_object_get_string(streamJ));
}
return 0;
diff --git a/plugins/alsa/alsa-api-sndzones.c b/plugins/alsa/alsa-api-sndzones.c
index 11d77c2..7ca61ad 100644
--- a/plugins/alsa/alsa-api-sndzones.c
+++ b/plugins/alsa/alsa-api-sndzones.c
@@ -128,12 +128,11 @@ CTLP_LUA2C(snd_zones, source, argsJ, responseJ) {
// instantiate one route PCM per zone with multi plugin as slave
for (int idx = 0; sndZone[idx].uid != NULL; idx++) {
- Softmixer->zonePcms[idx] = AlsaCreateRoute(source, &sndZone[idx]);
+ Softmixer->zonePcms[idx] = AlsaCreateRoute(source, &sndZone[idx], 0);
if (!Softmixer->zonePcms[idx]) {
AFB_ApiNotice(source->api, "L2C:sndzones fail to create route zone=%s", sndZone[idx].uid);
goto OnErrorExit;
}
- snd_pcm_close(Softmixer->zonePcms[idx]->handle);
}
// do not need this handle anymore
diff --git a/plugins/alsa/alsa-core-ctl.c b/plugins/alsa/alsa-core-ctl.c
index 38ce8cc..79bf0c8 100644
--- a/plugins/alsa/alsa-core-ctl.c
+++ b/plugins/alsa/alsa-core-ctl.c
@@ -46,11 +46,13 @@ typedef struct {
typedef struct {
SubStreamT stream[MAX_AUDIO_STREAMS + 1];
int count;
+ snd_ctl_t *ctlDev;
} AudioStreamHandleT;
static AudioStreamHandleT AudioStreamHandle;
-STATIC snd_ctl_elem_id_t *AlsaCtlGetElemId(CtlSourceT *source, snd_ctl_t* ctlDev, int numid) {
+
+PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNumidElemId(CtlSourceT *source, snd_ctl_t* ctlDev, int numid) {
char string[32];
int error;
int index;
@@ -60,18 +62,18 @@ STATIC snd_ctl_elem_id_t *AlsaCtlGetElemId(CtlSourceT *source, snd_ctl_t* ctlDev
snd_ctl_elem_list_alloca(&ctlList);
if ((error = snd_ctl_elem_list(ctlDev, ctlList)) < 0) {
- AFB_ApiError(source->api, "AlsaCtlElemIdGetInt [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string));
+ AFB_ApiError(source->api, "AlsaCtlGetNumidElemId [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string));
goto OnErrorExit;
}
if ((error = snd_ctl_elem_list_alloc_space(ctlList, snd_ctl_elem_list_get_count(ctlList))) < 0) {
- AFB_ApiError(source->api, "AlsaCtlElemIdGetInt [%s] fail retrieve count", ALSA_CTL_UID(ctlDev, string));
+ AFB_ApiError(source->api, "AlsaCtlGetNumidElemId [%s] fail retrieve count", ALSA_CTL_UID(ctlDev, 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, "AlsaCtlElemIdGetInt [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string));
+ AFB_ApiError(source->api, "AlsaCtlGetNumidElemId [%s] fail retrieve controls", ALSA_CTL_UID(ctlDev, string));
goto OnErrorExit;
}
@@ -87,7 +89,7 @@ STATIC snd_ctl_elem_id_t *AlsaCtlGetElemId(CtlSourceT *source, snd_ctl_t* ctlDev
}
if (index == ctlCount) {
- AFB_ApiError(source->api, "AlsaCtlRegister [%s] fail get numid=%i count", ALSA_CTL_UID(ctlDev, string), numid);
+ AFB_ApiError(source->api, "AlsaCtlGetNumidElemId [%s] fail get numid=%i count", ALSA_CTL_UID(ctlDev, string), numid);
goto OnErrorExit;
}
@@ -100,6 +102,55 @@ OnErrorExit:
return NULL;
}
+PUBLIC snd_ctl_elem_id_t *AlsaCtlGetNameElemId(CtlSourceT *source, snd_ctl_t* ctlDev, const char *ctlName) {
+ char string[32];
+ int error;
+ int index;
+ snd_ctl_elem_list_t *ctlList = NULL;
+ snd_ctl_elem_id_t *elemId;
+
+ 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));
+ 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));
+ 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));
+ goto OnErrorExit;
+ }
+
+ // loop on control to find the right one
+ 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))) {
+ snd_ctl_elem_id_malloc(&elemId);
+ snd_ctl_elem_list_get_id(ctlList, index, elemId);
+ break;
+ }
+ }
+
+ if (index == ctlCount) {
+ AFB_ApiError(source->api, "AlsaCtlGetNameElemId [%s] fail get ctl name=%s", ALSA_CTL_UID(ctlDev, string), ctlName);
+ goto OnErrorExit;
+ }
+
+ // clear ctl list and return elemid
+ snd_ctl_elem_list_clear(ctlList);
+ return elemId;
+
+OnErrorExit:
+ if (ctlList) snd_ctl_elem_list_clear(ctlList);
+ return NULL;
+}
PUBLIC snd_ctl_t *AlsaCtlOpenCtl(CtlSourceT *source, const char *cardid) {
int error;
@@ -135,7 +186,50 @@ OnErrorExit:
return -1;
}
-STATIC int CtlElemIdGetInt(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, long *value) {
+STATIC void CtlElemIdDisplay(AFB_ApiT api, 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);
+ const char* name = snd_ctl_elem_info_get_name(elemInfo);
+ snd_ctl_elem_type_t elemType = snd_ctl_elem_info_get_type(elemInfo);
+
+
+ if (!elemData) {
+ AFB_ApiWarning(api, "CtlElemIdDisplay: numid=%d name=%s value=unreadable", numid, name);
+ } else
+ for (int idx = 0; idx < count; idx++) {
+ long valueL;
+
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ break;
+ case SND_CTL_ELEM_TYPE_IEC958:
+ default:
+ AFB_ApiWarning(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) {
int error;
snd_ctl_elem_value_t *elemData;
snd_ctl_elem_info_t *elemInfo;
@@ -146,58 +240,68 @@ STATIC int CtlElemIdGetInt(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *e
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_type_t elemType = snd_ctl_elem_info_get_type(elemInfo);
- int count = snd_ctl_elem_info_get_count(elemInfo);
- if (count != 1) goto OnErrorExit;
snd_ctl_elem_value_alloca(&elemData);
snd_ctl_elem_value_set_id(elemData, elemId);
error = snd_ctl_elem_read(ctlDev, elemData);
- if (error) goto OnSuccessExit;
+ 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 = snd_ctl_elem_value_get_integer(elemData, 0);
+ *value = (int) snd_ctl_elem_value_get_integer(elemData, 0);
-OnSuccessExit:
return 0;
OnErrorExit:
+ CtlElemIdDisplay(api, elemInfo, elemData);
+ return -1;
+}
- AFB_ApiWarning(api, "CtlSubscribeEventCB: ignored unsupported event Numid=%i", snd_ctl_elem_info_get_numid(elemInfo));
- for (int idx = 0; idx < count; idx++) {
- long valueL;
-
- switch (elemType) {
- case SND_CTL_ELEM_TYPE_BOOLEAN:
- valueL = snd_ctl_elem_value_get_boolean(elemData, idx);
- AFB_ApiNotice(api, "CtlElemIdGetBool: value=%ld", valueL);
- break;
- case SND_CTL_ELEM_TYPE_INTEGER:
- valueL = snd_ctl_elem_value_get_integer(elemData, idx);
- AFB_ApiNotice(api, "CtlElemIdGetInt: value=%ld", valueL);
- break;
- case SND_CTL_ELEM_TYPE_INTEGER64:
- valueL = snd_ctl_elem_value_get_integer64(elemData, idx);
- AFB_ApiNotice(api, "CtlElemIdGetInt64: value=%ld", valueL);
- break;
- case SND_CTL_ELEM_TYPE_ENUMERATED:
- valueL = snd_ctl_elem_value_get_enumerated(elemData, idx);
- AFB_ApiNotice(api, "CtlElemIdGetEnum: value=%ld", valueL);
- break;
- case SND_CTL_ELEM_TYPE_BYTES:
- valueL = snd_ctl_elem_value_get_byte(elemData, idx);
- AFB_ApiNotice(api, "CtlElemIdGetByte: value=%ld", valueL);
- break;
- case SND_CTL_ELEM_TYPE_IEC958:
- default:
- AFB_ApiNotice(api, "CtlElemIdGetInt: Unsupported type=%d", elemType);
- break;
- }
+PUBLIC int CtlElemIdSetLong(AFB_ApiT api, snd_ctl_t *ctlDev, snd_ctl_elem_id_t *elemId, long value) {
+ snd_ctl_elem_value_t *elemData;
+ snd_ctl_elem_info_t *elemInfo;
+ const char* name;
+ int error, numid;
+
+ 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_writable(elemInfo)) goto OnErrorExit;
+
+ int count = snd_ctl_elem_info_get_count(elemInfo);
+ if (count == 0) goto OnErrorExit;
+
+ snd_ctl_elem_value_alloca(&elemData);
+ snd_ctl_elem_value_set_id(elemData, elemId);
+ error = snd_ctl_elem_read(ctlDev, 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);
+ if (error) goto OnErrorExit;
+
+ return 0;
+
+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);
return -1;
}
// Clone of AlsaLib snd_card_load2 static function
+
PUBLIC snd_ctl_card_info_t *AlsaCtlGetInfo(CtlSourceT *source, const char *cardid) {
int error;
snd_ctl_t *ctlDev;
@@ -220,15 +324,34 @@ OnErrorExit:
return NULL;
}
-PUBLIC int AlsaCtlGetNumidValueI(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long* value) {
-
- snd_ctl_elem_id_t *elemId = AlsaCtlGetElemId(source, ctlDev, numid);
+PUBLIC int AlsaCtlNumidSetLong(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long value) {
+
+ snd_ctl_elem_id_t *elemId = AlsaCtlGetNumidElemId(source, ctlDev, numid);
+ if (!elemId) {
+ AFB_ApiError(source->api, "AlsaCtlNumidSetLong [sndcard=%s] fail to find numid=%d", snd_ctl_name(ctlDev), numid);
+ goto OnErrorExit;
+ }
+
+ int error = CtlElemIdSetLong(source->api, ctlDev, elemId, value);
+ if (error) {
+ AFB_ApiError(source->api, "AlsaCtlNumidSetLong [sndcard=%s] fail to set numid=%d value=%ld", snd_ctl_name(ctlDev), numid, value);
+ goto OnErrorExit;
+ }
+
+ return 0;
+OnErrorExit:
+ return -1;
+}
+
+PUBLIC int AlsaCtlNumidGetLong(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long* value) {
+
+ snd_ctl_elem_id_t *elemId = AlsaCtlGetNumidElemId(source, ctlDev, numid);
if (!elemId) {
AFB_ApiError(source->api, "AlsaCtlGetNumValueI [sndcard=%s] fail to find numid=%d", snd_ctl_name(ctlDev), numid);
goto OnErrorExit;
}
- int error = CtlElemIdGetInt(source->api, ctlDev, elemId, value);
+ int error = CtlElemIdGetLong(source->api, ctlDev, elemId, value);
if (error) {
AFB_ApiError(source->api, "AlsaCtlGetNumValueI [sndcard=%s] fail to get numid=%d value", snd_ctl_name(ctlDev), numid);
goto OnErrorExit;
@@ -239,6 +362,79 @@ 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) {
+ snd_ctl_elem_type_t ctlType;
+ snd_ctl_elem_info_t *elemInfo;
+ int error;
+
+ 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);
+
+ // softvol plugin is bugged and can only 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, 0);
+ snd_ctl_elem_info_set_subdevice(elemInfo, 0);
+
+ // only two types implemented
+ if (ctlMin == 0 && ctlMax == 1) ctlType = SND_CTL_ELEM_TYPE_BOOLEAN;
+ else ctlType = SND_CTL_ELEM_TYPE_INTEGER;
+
+ switch (ctlType) {
+ case SND_CTL_ELEM_TYPE_BOOLEAN:
+ error = snd_ctl_add_boolean_elem_set(ctlDev, 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);
+ if (error) goto OnErrorExit;
+ break;
+
+ default:
+ AFB_ApiError(source->api, "AlsaCtlMakeControl:%s(subdev) fail to create %s(control)", subdev->uid, ctlName);
+ goto OnErrorExit;
+ }
+
+ // retrieve newly created control numid
+ int numid = snd_ctl_elem_info_get_numid(elemInfo);
+ return numid;
+
+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) {
+ int numid = -1;
+
+ // if control does not exist then create
+ snd_ctl_elem_id_t *elemId = AlsaCtlGetNameElemId(source, ctlDev, 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);
+ if (numid <= 0) {
+ AFB_ApiError(source->api, "AlsaCtlCreateControl [sndcard=%s] fail to create ctlName=%s", snd_ctl_name(ctlDev), ctlName);
+ goto OnErrorExit;
+ }
+
+ elemId = AlsaCtlGetNumidElemId(source, ctlDev, numid);
+ }
+
+ int error = CtlElemIdSetLong(source->api, ctlDev, elemId, value);
+ if (error) {
+ AFB_ApiError(source->api, "AlsaCtlCreateControl [sndcard=%s] fail to set ctlName=%s Numid=%d", snd_ctl_name(ctlDev), ctlName, numid);
+ goto OnErrorExit;
+ }
+
+ 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;
@@ -270,7 +466,7 @@ 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 = CtlElemIdGetInt(subscribeHandle->api, subscribeHandle->ctlDev, elemId, &value);
+ error = CtlElemIdGetLong(subscribeHandle->api, subscribeHandle->ctlDev, elemId, &value);
if (error) goto OnErrorExit;
error = CtlElemIdGetNumid(subscribeHandle->api, subscribeHandle->ctlDev, elemId, &numid);
@@ -286,15 +482,15 @@ STATIC int CtlSubscribeEventCB(sd_event_source* src, int fd, uint32_t revents, v
}
if (idx == AudioStreamHandle.count) {
char cardName[32];
- ALSA_CTL_UID(subscribeHandle->ctlDev,cardName);
- AFB_ApiWarning(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d card=%s numid=%d (ignored)", subscribeHandle->info, subscribeHandle->tid, cardName, numid);
+ ALSA_CTL_UID(subscribeHandle->ctlDev, cardName);
+ AFB_ApiWarning(subscribeHandle->api, "CtlSubscribeEventCB:%s/%d card=%s numid=%d (ignored)", subscribeHandle->info, subscribeHandle->tid, cardName, numid);
}
-
+
OnSuccessExit:
return 0;
OnErrorExit:
- AFB_ApiWarning(subscribeHandle->api, "CtlSubscribeEventCB: ignored unsupported event");
+ AFB_ApiInfo(subscribeHandle->api, "CtlSubscribeEventCB: ignored unsupported event");
return 0;
}
@@ -340,7 +536,6 @@ OnErrorExit:
return NULL;
}
-
PUBLIC int AlsaCtlSubscribe(CtlSourceT *source, snd_ctl_t * ctlDev) {
int error;
char string [32];
@@ -389,43 +584,32 @@ OnErrorExit:
}
PUBLIC int AlsaCtlRegister(CtlSourceT *source, AlsaPcmInfoT *pcm, int numid) {
- long value;
- int error;
- // NumID are attached to sndcard retrieve ctldev from PCM
- snd_ctl_t* ctlDev = AlsaCrlFromPcm(source, pcm->handle);
- if (!ctlDev) {
- AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail attache sndcard", pcm->cardid);
+
+ int count = AudioStreamHandle.count;
+ if (count > MAX_AUDIO_STREAMS) {
+ AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] to many audio stream max=%d", pcm->cardid, MAX_AUDIO_STREAMS);
goto OnErrorExit;
}
- // This is the first registration let's subscrive to event
- if (AudioStreamHandle.count == 0) {
+ // If 1st registration then open a dev control channel to recieve events
+ if (!AudioStreamHandle.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);
}
- error = AlsaCtlGetNumidValueI(source, ctlDev, numid, &value);
- if (error) goto OnErrorExit;
-
- AFB_ApiNotice(source->api, "AlsaCtlRegister [pcm=%s] numid=%d value=%ld", pcm->cardid, numid, value);
-
// store PCM in order to pause/resume depending on event
- int count=AudioStreamHandle.count;
AudioStreamHandle.stream[count].pcm = pcm;
AudioStreamHandle.stream[count].numid = numid;
-
- // we only need to keep ctldev open for initial registration
- if (AudioStreamHandle.count++ > 0) snd_ctl_close(ctlDev);
-
- // toggle pause/resume (should be done after pcm_start)
- if ((error = snd_pcm_pause(pcm->handle, !value)) < 0) {
- AFB_ApiError(source->api, "AlsaCtlRegister [pcm=%s] fail to pause", pcm->cardid);
- goto OnErrorExit;
- }
+ AudioStreamHandle.count++;
return 0;
OnErrorExit:
-
return -1;
}
diff --git a/plugins/alsa/alsa-core-pcm.c b/plugins/alsa/alsa-core-pcm.c
index f143eb6..bc676ef 100644
--- a/plugins/alsa/alsa-core-pcm.c
+++ b/plugins/alsa/alsa-core-pcm.c
@@ -29,9 +29,6 @@ for the specific language governing permissions and
#include <sys/syscall.h>
-
-#define BUFFER_FRAME_COUNT 1024
-
typedef struct {
snd_pcm_t *pcmIn;
snd_pcm_t *pcmOut;
@@ -238,7 +235,7 @@ STATIC int AlsaPcmReadCB(sd_event_source* src, int fd, uint32_t revents, void* u
// In/Out frames transfer through buffer copy
framesOut = snd_pcm_writei(pcmCopyHandle->pcmOut, pcmCopyHandle->buffer, framesIn);
if (framesOut < 0 || framesOut != framesIn) {
- AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PcmOut=%s UNDERUN/SUSPEND frameOut=%ld", ALSA_PCM_UID(pcmCopyHandle->pcmOut, string), framesOut);
+ AFB_ApiNotice(pcmCopyHandle->api, "AlsaPcmReadCB PcmOut=%s UNDERUN frame=%ld", ALSA_PCM_UID(pcmCopyHandle->pcmOut, string), (framesIn - framesOut));
goto ExitOnSuccess;
}
@@ -283,28 +280,26 @@ PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pc
struct pollfd *pcmInFds;
int error;
- // prepare PCM for capture and replay
- error = AlsaPcmConf(source, pcmIn, opts);
+ // input and output should match
+ error = AlsaPcmConf(source, pcmOut, 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));
+ 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));
goto OnErrorExit;
};
-
- error = AlsaPcmConf(source, pcmOut, opts);
+ // prepare PCM for capture and replay
+ error = AlsaPcmConf(source, pcmIn, opts);
if (error) goto OnErrorExit;
// Prepare PCM for usage
- if ((error = snd_pcm_prepare(pcmOut->handle)) < 0) {
- AFB_ApiError(source->api, "AlsaPcmCopy: Fail to start PCM=%s error=%s", ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error));
+ 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));
goto OnErrorExit;
};
-
-
AlsaPcmCopyHandleT *pcmCopyHandle = malloc(sizeof (AlsaPcmCopyHandleT));
pcmCopyHandle->info = "pcmCpy";
pcmCopyHandle->pcmIn = pcmIn->handle;
@@ -312,7 +307,7 @@ PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pc
pcmCopyHandle->api = source->api;
pcmCopyHandle->channels = opts->channels;
pcmCopyHandle->frameSize = opts->channels * opts->sampleSize;
- pcmCopyHandle->frameCount = BUFFER_FRAME_COUNT;
+ pcmCopyHandle->frameCount = ALSA_BUFFER_FRAMES_COUNT;
pcmCopyHandle->buffer = malloc(pcmCopyHandle->frameCount * pcmCopyHandle->frameSize);
// get FD poll descriptor for capture PCM
@@ -350,8 +345,8 @@ PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *pc
return 0;
OnErrorExit:
- AFB_ApiError(source->api, "AlsaPcmCopy: Fail \n - pcmIn=%s \n - pcmOut=%s", ALSA_PCM_UID(pcmIn->handle, string), ALSA_PCM_UID(pcmOut->handle, string));
-
+ 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));
return -1;
}
diff --git a/plugins/alsa/alsa-plug-dmix.c b/plugins/alsa/alsa-plug-dmix.c
index 22b5117..b06d5f6 100644
--- a/plugins/alsa/alsa-plug-dmix.c
+++ b/plugins/alsa/alsa-plug-dmix.c
@@ -25,7 +25,7 @@ static int uniqueIpcIndex = 1024;
ALSA_PLUG_PROTO(dmix);
-PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave) {
+PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave, int open) {
snd_config_t *dmixConfig, *slaveConfig, *elemConfig, *pcmConfig;
AlsaPcmInfoT *pcmPlug= calloc(1,sizeof(AlsaPcmInfoT));
@@ -57,7 +57,7 @@ PUBLIC AlsaPcmInfoT* AlsaCreateDmix(CtlSourceT *source, const char* pcmName, Als
error += snd_config_add(dmixConfig, slaveConfig);
if (error) goto OnErrorExit;
- error = _snd_pcm_dmix_open(&pcmPlug->handle, pcmPlug->cardid, snd_config, dmixConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK);
+ if (open) error = _snd_pcm_dmix_open(&pcmPlug->handle, pcmPlug->cardid, snd_config, dmixConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK);
if (error) {
AFB_ApiError(source->api, "AlsaCreateDmix: fail to create Dmix=%s Slave=%s Error=%s", pcmPlug->cardid, pcmSlave->cardid, snd_strerror(error));
goto OnErrorExit;
diff --git a/plugins/alsa/alsa-plug-multi.c b/plugins/alsa/alsa-plug-multi.c
index 6deca96..3e82e1e 100644
--- a/plugins/alsa/alsa-plug-multi.c
+++ b/plugins/alsa/alsa-plug-multi.c
@@ -22,7 +22,7 @@
ALSA_PLUG_PROTO(multi);
-PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmUid) {
+PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmUid, int open) {
snd_config_t *multiConfig, *elemConfig, *slavesConfig, *slaveConfig, *bindingsConfig, *bindingConfig, *pcmConfig;
int error = 0, channelIdx=0;
@@ -79,7 +79,7 @@ PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmUid) {
// update top config to access previous plugin PCM
snd_config_update();
- error = _snd_pcm_multi_open(&pcmPlug->handle, pcmUid, snd_config, multiConfig, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+ if (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;
diff --git a/plugins/alsa/alsa-plug-rate.c b/plugins/alsa/alsa-plug-rate.c
new file mode 100644
index 0000000..a9b9e23
--- /dev/null
+++ b/plugins/alsa/alsa-plug-rate.c
@@ -0,0 +1,81 @@
+/*
+ * 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(rate);
+
+
+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;
+
+ 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_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_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_add(pcmConfig, rateConfig);
+ if (error) {
+ AFB_ApiError(source->api, "AlsaCreateRate: fail to add configRATE=%s", pcmPlug->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 (error) {
+ AFB_ApiError(source->api, "AlsaCreateRate: fail to create Rate=%s Slave=%s Error=%s", pcmPlug->cardid, pcmSlave->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);
+ return pcmPlug;
+
+OnErrorExit:
+ AlsaDumpCtlConfig(source, "plug-rate", rateConfig, 1);
+ AFB_ApiNotice(source->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 2c8f3cb..8fa5de8 100644
--- a/plugins/alsa/alsa-plug-route.c
+++ b/plugins/alsa/alsa-plug-route.c
@@ -47,7 +47,7 @@ OnErrorExit:
return -1;
}
-PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone) {
+PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone, int open) {
snd_config_t *routeConfig, *elemConfig, *slaveConfig, *tableConfig, *pcmConfig;
int error = 0;
@@ -115,7 +115,7 @@ PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone) {
// update top config to access previous plugin PCM
snd_config_update();
- error = _snd_pcm_route_open(&pcmPlug->handle, zone->uid, snd_config, routeConfig, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
+ if (open) error = _snd_pcm_route_open(&pcmPlug->handle, zone->uid, snd_config, routeConfig, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (error) {
AFB_ApiError(source->api, "AlsaCreateRoute:zone(%s) fail to create Plug=%s error=%s", zone->uid, pcmPlug->cardid, snd_strerror(error));
goto OnErrorExit;
diff --git a/plugins/alsa/alsa-plug-stream.c b/plugins/alsa/alsa-plug-stream.c
index e0ef69a..ffb7010 100644
--- a/plugins/alsa/alsa-plug-stream.c
+++ b/plugins/alsa/alsa-plug-stream.c
@@ -36,11 +36,11 @@ STATIC AlsaPcmInfoT* SlaveZoneByUid(CtlSourceT *source, AlsaPcmInfoT **pcmZones
return NULL;
}
-PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *ctlControl) {
+PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *ctlControl, const char* ctlName, int open) {
snd_config_t *streamConfig, *elemConfig, *slaveConfig, *controlConfig,*pcmConfig;
int error = 0;
- AlsaPcmInfoT *pcmPlug= malloc(sizeof(AlsaPcmInfoT));
+ AlsaPcmInfoT *pcmPlug= calloc(1,sizeof(AlsaPcmInfoT));
// assert static/global softmixer handle get requited info
AlsaSndLoopT *ctlLoop = Softmixer->loopCtl;
@@ -57,16 +57,19 @@ PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream
}
// search for target zone uid
- pcmPlug->uid= stream->uid;
- pcmPlug->cardid= stream->uid;
AlsaPcmInfoT *pcmSlave= SlaveZoneByUid (source, pcmZones, stream->zone);
if (!pcmSlave || !pcmSlave->uid) {
- AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) fail to find Zone=%s", pcmPlug->uid, stream->zone);
+ AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) fail to find Zone=%s", stream->uid, stream->zone);
goto OnErrorExit;
}
// 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();
@@ -74,6 +77,8 @@ PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream
error += snd_config_set_id (streamConfig, pcmPlug->cardid);
error += snd_config_imake_string(&elemConfig, "type", "softvol");
error += snd_config_add(streamConfig, elemConfig);
+ error += snd_config_imake_integer(&elemConfig, "resolution", 99); // use 0-100%
+ error += snd_config_add(streamConfig, elemConfig);
if (error) goto OnErrorExit;
// add slave leaf
@@ -85,7 +90,7 @@ PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream
// add control leaf
error += snd_config_make_compound(&controlConfig, "control", 0);
- error += snd_config_imake_string(&elemConfig, "name", stream->uid);
+ 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_add(controlConfig, elemConfig);
@@ -95,7 +100,7 @@ PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream
// update top config to access previous plugin PCM
snd_config_update();
- error = _snd_pcm_softvol_open(&pcmPlug->handle, stream->uid, snd_config, streamConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK);
+ if (open) error = _snd_pcm_softvol_open(&pcmPlug->handle, stream->uid, snd_config, streamConfig, SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK);
if (error) {
AFB_ApiError(source->api, "AlsaCreateStream:%s(stream) fail to create Plug=%s Slave=%s error=%s", stream->uid, pcmPlug->cardid, pcmSlave->cardid, snd_strerror(error));
goto OnErrorExit;
diff --git a/plugins/alsa/alsa-softmixer.c b/plugins/alsa/alsa-softmixer.c
index bd9ee5e..23a01dd 100644
--- a/plugins/alsa/alsa-softmixer.c
+++ b/plugins/alsa/alsa-softmixer.c
@@ -33,85 +33,3 @@ CTLP_ONLOAD(plugin, callbacks) {
return NULL;
}
-CTLP_LUA2C(AlsaDmix, source, argsJ, responseJ) {
- json_object* subscribeArgsJ = NULL;
-
- int error = 0;
- wrap_json_pack(&subscribeArgsJ, "{ss}", "value", "location");
- AFB_ApiNotice(source->api, "lua2c router with %s", json_object_to_json_string_ext(subscribeArgsJ, JSON_C_TO_STRING_PRETTY));
-
- return error;
-}
-
-
-
-CTLP_LUA2C(AlsaRouter, source, argsJ, responseJ) {
- json_object *sndInJ, *sndOutJ, *paramsJ = NULL;
- AlsaPcmInfoT *sndIn, *sndOut;
- int error;
-
- // make sndIn/Out a pointer to get cleaner code
- sndIn = calloc(1, sizeof (AlsaPcmInfoT));
- sndOut = calloc(1, sizeof (AlsaPcmInfoT));
-
- // set pcm options to defaults
- AlsaPcmHwInfoT *pcmOpts;
- pcmOpts = calloc(1, sizeof (AlsaPcmHwInfoT));
- pcmOpts->format = SND_PCM_FORMAT_UNKNOWN;
- pcmOpts->access = SND_PCM_ACCESS_RW_INTERLEAVED;
-
- error = wrap_json_unpack(argsJ, "{s:o,s:o,s?o}", "devin", &sndInJ, "devout", &sndOutJ, "params", &paramsJ);
- if (error) {
- AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter ARGS missing devIn|devOut args=%s", json_object_get_string(argsJ));
- goto OnErrorExit;
- }
-
- error = wrap_json_unpack(sndInJ, "{s?s,s?s,s?i,s?i,s?i}", "path", &sndIn->devpath, "id", &sndIn->cardid, "numid", &sndIn->numid, "dev", &sndIn->device, "sub", &sndIn->subdev);
- if (error || (!sndIn->devpath && !sndIn->cardid)) {
- AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter DEV-IN missing 'path|id|dev|sub|numid' devin=%s", json_object_get_string(sndInJ));
- goto OnErrorExit;
- }
-
- error = wrap_json_unpack(sndOutJ, "{s?s,s?s,s?i,s?i, s?i}", "path", &sndOut->devpath, "id", &sndOut->cardid, "numid", &sndOut->numid, "dev", &sndOut->device, "sub", &sndOut->subdev);
- if (error || (!sndOut->devpath && !sndOut->cardid)) {
- AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter DEV-OUT missing 'path|id|dev|sub' devout=%s", json_object_get_string(sndOutJ));
- goto OnErrorExit;
- }
-
- if (paramsJ) if ((error = wrap_json_unpack(paramsJ, "{s?i, s?i, s?i, s?i}", "format", &pcmOpts->format, "access", &pcmOpts->access, "rate", &pcmOpts->rate, "channels", &pcmOpts->channels)) != 0) {
- AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter PARAMS missing 'format|access|rate|channels' params=%s", json_object_get_string(paramsJ));
- goto OnErrorExit;
- }
-
- AFB_ApiNotice(source->api, "--lua2c-- AlsaRouter devin=%s devout=%s rate=%d channel=%d", sndIn->devpath, sndOut->devpath, pcmOpts->rate, pcmOpts->channels);
-
- // Check sndOut Exist and build a valid cardid config
- error = AlsaByPathDevid(source, sndOut);
- if (error) goto OnErrorExit;
-
- // open capture PCM
- AlsaPcmInfoT *pcmIn = AlsaByPathOpenPcm(source, sndIn, SND_PCM_STREAM_CAPTURE);
- if (!pcmIn) goto OnErrorExit;
-
- AlsaPcmInfoT *pcmDmix = AlsaCreateDmix(source, "DmixPlugPcm", sndOut);
- if (!pcmDmix) goto OnErrorExit;
-
- //AlsaPcmInfoT *pcmVol = AlsaCreateVol(source, "SoftVol", sndIn, pcmDmix);
- //if (!pcmVol) goto OnErrorExit;
-
- //error = AlsaPcmCopy(source, pcmIn, pcmVol, pcmOpts);
- //if (error) goto OnErrorExit;
-
- // Registration to event should be done after pcm_start
- if (sndIn->numid) {
- error = AlsaCtlRegister(source, pcmIn, sndIn->numid);
- if (error) goto OnErrorExit;
- }
-
- return 0;
-
-OnErrorExit:
- AFB_ApiNotice(source->api, "--lua2c-- ERROR AlsaRouter sndIn=%s sndOut=%s rate=%d channel=%d", sndIn->devpath, sndOut->devpath, pcmOpts->rate, pcmOpts->channels);
- return -1;
-}
-
diff --git a/plugins/alsa/alsa-softmixer.h b/plugins/alsa/alsa-softmixer.h
index db499c3..3018402 100644
--- a/plugins/alsa/alsa-softmixer.h
+++ b/plugins/alsa/alsa-softmixer.h
@@ -33,9 +33,11 @@
#include <alsa/asoundlib.h>
#define MAINLOOP_WATCHDOG 30000
-#define MAX_AUDIO_STREAMS 8
+#define MAX_AUDIO_STREAMS 8*2
#define ALSA_DEFAULT_PCM_RATE 48000
-#define ALSA_CARDID_MAX_LEN 32
+#define ALSA_DEFAULT_PCM_VOLUME 80
+#define ALSA_BUFFER_FRAMES_COUNT 1024
+#define ALSA_CARDID_MAX_LEN 64
#define ALSA_PLUG_PROTO(plugin) \
@@ -110,11 +112,15 @@ typedef struct {
extern SoftMixerHandleT *Softmixer;
+// 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);
+// alsa-api-*.c
+PUBLIC int ProcessSndParams(CtlSourceT *source, const char* uid, json_object *paramsJ, AlsaPcmHwInfoT *params);
+
// 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);
@@ -130,18 +136,25 @@ PUBLIC char *AlsaDumpCtlUid(snd_ctl_t *ctlHandle, char *buffer, size_t len);
// 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);
PUBLIC int AlsaCtlRegister(CtlSourceT *source, AlsaPcmInfoT *pcm, int numid);
-PUBLIC int AlsaCtlGetNumidValueI(CtlSourceT *source, snd_ctl_t* ctlDev, int numid, long* value);
+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 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);
// alsa-core-pcm.c
PUBLIC int AlsaPcmConf(CtlSourceT *source, AlsaPcmInfoT *pcm, AlsaPcmHwInfoT *opts);
PUBLIC int AlsaPcmCopy(CtlSourceT *source, AlsaPcmInfoT *pcmIn, AlsaPcmInfoT *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);
-PUBLIC AlsaPcmInfoT* AlsaCreateMulti(CtlSourceT *source, const char *pcmName);
-PUBLIC AlsaPcmInfoT* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *pcmControl);
-PUBLIC AlsaPcmInfoT* AlsaCreateRoute(CtlSourceT *source, AlsaSndZoneT *zone);
+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* AlsaCreateStream(CtlSourceT *source, AlsaSndStreamT *stream, AlsaPcmInfoT *ctlControl, const char* ctlName, int open);
+PUBLIC AlsaPcmInfoT* AlsaCreateRate(CtlSourceT *source, const char* pcmName, AlsaPcmInfoT *pcmSlave, int open);
#endif \ No newline at end of file
diff --git a/plugins/alsa/alsa-utils-bypath.c b/plugins/alsa/alsa-utils-bypath.c
index 7ceb66f..a857ab1 100644
--- a/plugins/alsa/alsa-utils-bypath.c
+++ b/plugins/alsa/alsa-utils-bypath.c
@@ -74,7 +74,6 @@ PUBLIC int AlsaByPathDevid(CtlSourceT *source, AlsaPcmInfoT *dev) {
dev->cardid=malloc(ALSA_CARDID_MAX_LEN);
snprintf((char*)dev->cardid, ALSA_CARDID_MAX_LEN, "hw:%i", dev->cardidx);
cardInfo = AlsaCtlGetInfo(source, dev->cardid);
- cardInfo = AlsaCtlGetInfo(source, dev->cardid);
}
if (!cardInfo) {