diff options
-rw-r--r-- | CMakeLists.txt | 23 | ||||
-rw-r--r-- | LICENSE | 201 | ||||
-rw-r--r-- | README.md | 1 | ||||
-rw-r--r-- | conf.d/CMakeLists.txt | 22 | ||||
-rwxr-xr-x | conf.d/autobuild/agl/autobuild | 63 | ||||
-rwxr-xr-x | conf.d/autobuild/linux/autobuild | 63 | ||||
-rw-r--r-- | conf.d/cmake/config.cmake | 156 | ||||
-rw-r--r-- | conf.d/project/CMakeLists.txt | 24 | ||||
-rw-r--r-- | conf.d/project/alsa.d/asoundrc.sample | 146 | ||||
-rw-r--r-- | conf.d/project/alsa.d/ucm.sample/HDA Intel PCH.conf | 6 | ||||
-rw-r--r-- | conf.d/project/alsa.d/ucm.sample/HiFi.conf | 84 | ||||
-rw-r--r-- | conf.d/project/alsa.d/ucm.sample/README | 2 | ||||
-rw-r--r-- | src/CMakeLists.txt | 52 | ||||
-rw-r--r-- | src/ahl-apidef.h | 297 | ||||
-rw-r--r-- | src/ahl-apidef.json | 601 | ||||
-rw-r--r-- | src/ahl-binding.c | 450 | ||||
-rw-r--r-- | src/ahl-binding.h | 96 | ||||
-rw-r--r-- | src/ahl-deviceenum.c | 40 |
18 files changed, 2327 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2f42d5a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,23 @@ +########################################################################### +# Copyright 2017 Audiokinetic.com +# +# author: Francois Thibault <fthibault@audiokinetic.com> +# +# 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. +########################################################################### + +CMAKE_MINIMUM_REQUIRED(VERSION 3.5) + +# Do not change this file, config is located into conf.d +include(${CMAKE_CURRENT_SOURCE_DIR}/conf.d/cmake/config.cmake) + @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d73cf13 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# afb-audiohighlevel diff --git a/conf.d/CMakeLists.txt b/conf.d/CMakeLists.txt new file mode 100644 index 0000000..28a0609 --- /dev/null +++ b/conf.d/CMakeLists.txt @@ -0,0 +1,22 @@ +########################################################################### +# Copyright 2015, 2016, 2017 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. +########################################################################### + + +# Include any directory not starting with _ +# ----------------------------------------------------- +PROJECT_SUBDIRS_ADD(${PROJECT_SRC_DIR_PATTERN}) diff --git a/conf.d/autobuild/agl/autobuild b/conf.d/autobuild/agl/autobuild new file mode 100755 index 0000000..4811441 --- /dev/null +++ b/conf.d/autobuild/agl/autobuild @@ -0,0 +1,63 @@ +#!/usr/bin/make -f +# Copyright (C) 2015, 2016 "IoT.bzh" +# Author "Romain Forlot" <romain.forlot@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. + +THISFILE := $(lastword $(MAKEFILE_LIST)) +BUILD_DIR := $(abspath $(dir $(THISFILE)/../../../../..)/build) +DEST := ${BUILD_DIR}/target + +.PHONY: all clean distclean configure build package help update + +all: help + +help: + @echo "List of targets available:" + @echo "" + @echo "- all" + @echo "- clean" + @echo "- distclean" + @echo "- configure" + @echo "- build" + @echo "- package" + @echo "" + @echo "Usage: ./conf.d/autobuild/agl/autobuild package DEST=${HOME}/opt" + @echo "Don't use your build dir as DEST as wgt file is generated at this location" + +update: configure + @cmake --build ${BUILD_DIR} --target autobuild + +clean: + @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} clean) || echo Nothing to clean + +distclean: + @rm -rf ${BUILD_DIR} + +configure: ${BUILD_DIR}/Makefile + +build: configure + @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all + +package: build + @mkdir -p ${BUILD_DIR}/$@/bin + @mkdir -p ${BUILD_DIR}/$@/etc + @mkdir -p ${BUILD_DIR}/$@/lib + @mkdir -p ${BUILD_DIR}/$@/htdocs + @mkdir -p ${BUILD_DIR}/$@/data + @cmake --build ${BUILD_DIR} --target widget + @mkdir -p ${DEST} && cp ${BUILD_DIR}/*wgt ${DEST} + +${BUILD_DIR}/Makefile: + @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR} + @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..) diff --git a/conf.d/autobuild/linux/autobuild b/conf.d/autobuild/linux/autobuild new file mode 100755 index 0000000..4811441 --- /dev/null +++ b/conf.d/autobuild/linux/autobuild @@ -0,0 +1,63 @@ +#!/usr/bin/make -f +# Copyright (C) 2015, 2016 "IoT.bzh" +# Author "Romain Forlot" <romain.forlot@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. + +THISFILE := $(lastword $(MAKEFILE_LIST)) +BUILD_DIR := $(abspath $(dir $(THISFILE)/../../../../..)/build) +DEST := ${BUILD_DIR}/target + +.PHONY: all clean distclean configure build package help update + +all: help + +help: + @echo "List of targets available:" + @echo "" + @echo "- all" + @echo "- clean" + @echo "- distclean" + @echo "- configure" + @echo "- build" + @echo "- package" + @echo "" + @echo "Usage: ./conf.d/autobuild/agl/autobuild package DEST=${HOME}/opt" + @echo "Don't use your build dir as DEST as wgt file is generated at this location" + +update: configure + @cmake --build ${BUILD_DIR} --target autobuild + +clean: + @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} clean) || echo Nothing to clean + +distclean: + @rm -rf ${BUILD_DIR} + +configure: ${BUILD_DIR}/Makefile + +build: configure + @cmake --build ${BUILD_DIR} ${BUILD_ARGS} --target all + +package: build + @mkdir -p ${BUILD_DIR}/$@/bin + @mkdir -p ${BUILD_DIR}/$@/etc + @mkdir -p ${BUILD_DIR}/$@/lib + @mkdir -p ${BUILD_DIR}/$@/htdocs + @mkdir -p ${BUILD_DIR}/$@/data + @cmake --build ${BUILD_DIR} --target widget + @mkdir -p ${DEST} && cp ${BUILD_DIR}/*wgt ${DEST} + +${BUILD_DIR}/Makefile: + @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR} + @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CONFIGURE_ARGS} ..) diff --git a/conf.d/cmake/config.cmake b/conf.d/cmake/config.cmake new file mode 100644 index 0000000..08e79fc --- /dev/null +++ b/conf.d/cmake/config.cmake @@ -0,0 +1,156 @@ +########################################################################### +# Copyright 2017 Audiokinetic +# +# author: Tai Vuong <tvuong@audiokinetic.com> +# +# 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. +########################################################################### + +# Project Info +# ------------------ +set(PROJECT_NAME afb-audiohighlevel) +set(PROJECT_VERSION "0.1") +set(PROJECT_PRETTY_NAME "Audio High Level Binding") +set(PROJECT_DESCRIPTION "Give an High Level Binding for all AGL applications") +set(PROJECT_URL "https://github.com/Audiokinetic-Automotive/afb-audiohighlevel") +set(PROJECT_ICON "icon.png") +set(PROJECT_AUTHOR "Tai, Vuong") +set(PROJECT_AUTHOR_MAIL "tvuong@audiokinetic.com") +set(PROJECT_LICENCE "Apache-V2") +set(PROJECT_LANGUAGES,"C") + + +# Where are stored default templates files from submodule or subtree app-templates in your project tree +# relative to the root project directory +set(PROJECT_APP_TEMPLATES_DIR "conf.d/app-templates") + +# Use any directory that does not start with _ as valid source rep +set(PROJECT_SRC_DIR_PATTERN "[^_]*") + +# Compilation Mode (DEBUG, RELEASE) +# ---------------------------------- +set(CMAKE_BUILD_TYPE "DEBUG") + +# Compiler selection if needed. Overload the detected compiler. +# ----------------------------------------------- +set (gcc_minimal_version 4.9) +#set(CMAKE_C_COMPILER "gcc") +#set(CMAKE_CXX_COMPILER "g++") + +# When Present LUA is used by the controller +# --------------------------------------------------------------- +set(CONTROL_SUPPORT_LUA 1 CACHE BOOL "Active or not LUA Support") + +# PKG_CONFIG required packages +# ----------------------------- +set (PKG_REQUIRED_LIST + alsa>=1.1.2 + libsystemd>=222 + libmicrohttpd>=0.9.55 + afb-daemon + json-c + libafbwsc +) + +# Compilation options definition +# Use CMake generator expressions to specify only for a specific language +# Values are prefilled with default options that is currently used. +# Either separate options with ";", or each options must be quoted separately +# DO NOT PUT ALL OPTION QUOTED AT ONCE , COMPILATION COULD FAILED ! +# ---------------------------------------------------------------------------- +set(COMPILE_OPTIONS +-Wall +-Wextra +-Wconversion +-Wno-unused-parameter +-Wno-sign-compare +-Wno-sign-conversion +-Werror=maybe-uninitialized +-Werror=implicit-function-declaration +-ffunction-sections +-fdata-sections +-fPIC +# Personal compilation options +-DMAX_SND_CARD=16 # should be more than enough even in luxury vehicule +-DMAX_LINEAR_DB_SCALE=24 # until 24db volume normalisation use a simple linear scale +-DTLV_BYTE_SIZE=256 # Alsa use 4096 as default but 256 should fit most sndcards +-DCONTROL_MAXPATH_LEN=255 + CACHE STRING "Compilation flags") +#set(C_COMPILE_OPTIONS "" CACHE STRING "Compilation flags for C language.") +#set(CXX_COMPILE_OPTIONS "" CACHE STRING "Compilation flags for C++ language.") +#set(PROFILING_COMPILE_OPTIONS -g -O0 -pg -Wp,-U_FORTIFY_SOURCE CACHE STRING "Compilation flags for PROFILING build type.") +#set(DEBUG_COMPILE_OPTIONS -g -ggdb -Wp,-U_FORTIFY_SOURCE CACHE STRING "Compilation flags for DEBUG build type.") +#set(CCOV_COMPILE_OPTIONS -g -O2 --coverage CACHE STRING "Compilation flags for CCOV build type.") +#set(RELEASE_COMPILE_OPTIONS -g -O2 CACHE STRING "Compilation flags for RELEASE build type.") + +# Print a helper message when every thing is finished +# ---------------------------------------------------- +if(IS_DIRECTORY $ENV{HOME}/opt/afb-monitoring) +set(MONITORING_ALIAS "--alias=/monitoring:$ENV{HOME}/opt/afb-monitoring") +endif() + + + +# Optional location for config.xml.in +# ----------------------------------- +#set(WIDGET_ICON conf.d/wgt/${PROJECT_ICON} CACHE PATH "Path to the widget icon") +#set(WIDGET_CONFIG_TEMPLATE ${CMAKE_CURRENT_SOURCE_DIR}/conf.d/wgt/config.xml.in CACHE PATH "Path to widget config file template (config.xml.in)") + +# Optional dependencies order +# --------------------------- +#set(EXTRA_DEPENDENCIES_ORDER) + +# Optional Extra global include path +# ----------------------------------- +# set(EXTRA_INCLUDE_DIRS) + +# Optional extra libraries +# ------------------------- +# set(EXTRA_LINK_LIBRARIES) + +# Optional force binding installation +# ------------------------------------ +# set(BINDINGS_INSTALL_PREFIX PrefixPath ) + +# Optional force widget prefix generation +# ------------------------------------------------ +# set(WIDGET_PREFIX DestinationPath) + +# Optional Widget entry point file. +# --------------------------------------------------------- +# This is the file that will be executed, loaded,... +# at launch time by the application framework +set(WIDGET_ENTRY_POINT lib/afb-audiohighlevel.so) + +# Optional Widget Mimetype specification +# -------------------------------------------------- +# Choose between : +# - application/x-executable +# - application/vnd.agl.url +# - application/vnd.agl.service +# - application/vnd.agl.native +# - text/vnd.qt.qml +# - application/vnd.agl.qml +# - application/vnd.agl.qml.hybrid +# - application/vnd.agl.html.hybrid +# +set(WIDGET_TYPE application/vnd.agl.service) + +# Optional force binding Linking flag +# ------------------------------------ +# set(BINDINGS_LINK_FLAG LinkOptions ) + +# This include is mandatory and MUST happens at the end +# of this file, else you expose you to unexpected behavior +# ----------------------------------------------------------- +include(${PROJECT_APP_TEMPLATES_DIR}/cmake/common.cmake) diff --git a/conf.d/project/CMakeLists.txt b/conf.d/project/CMakeLists.txt new file mode 100644 index 0000000..fd4d454 --- /dev/null +++ b/conf.d/project/CMakeLists.txt @@ -0,0 +1,24 @@ +########################################################################### +# Copyright 2015, 2016, 2017 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. +########################################################################### + + + +# Include anything not starting with _ +PROJECT_SUBDIRS_ADD(${PROJECT_SRC_DIR_PATTERN}) + + diff --git a/conf.d/project/alsa.d/asoundrc.sample b/conf.d/project/alsa.d/asoundrc.sample new file mode 100644 index 0000000..8976077 --- /dev/null +++ b/conf.d/project/alsa.d/asoundrc.sample @@ -0,0 +1,146 @@ +# +# Author: Fulup Ar Foll +# Object: PCM hook type +# +# Test : Note: Jabra_USB=hw:v1340 +# Check SoundCard speaker-test -Dhw:v1340 -c2 -twav +# Check MixerPCM speaker-test -DMyMixerPCM -c2 -twav +# Check HookPCM speaker-test -DMyNavigationHook -c2 -twav +# Check NavPCM speaker-test -DMyNavPCM -c2 -twav +# MultiMedia aplay -DDMyNavPCM /usr/share/sounds/alsa/test.wav +# +# Bug/Feature: when softvol control is initialised from plugin and not +# from AGL binding. At 1st run ctl has invalid TLV and cannot be +# use. Bypass Solution: +# * start audio-binder before playing sound (binding create control before softvol plugin) +# * run a dummy aplay -DMyNavPCM "" to get a clean control +# +# References: https://www.spinics.net/lists/alsa-devel/msg54235.html +# -------------------------------------------------------------------- + +# Mixer PCM allow to play multiple stream simultaneously +# ------------------------------------------------------ +pcm.MyMixerPCM { + type dmix + ipc_key 1024 + ipc_key_add_uid false + ipc_perm 0666 # mixing for all users + + # Define target effective sound card (cannot be a plugin) + slave { + pcm "hw:v1340" #Jabra Solmate + period_time 0 + period_size 1024 + buffer_size 8192 + rate 44100 + } + + # DMIX can only map two channels + bindings { + 0 0 + 1 1 + } +} + +# Define a Hook_type with a private sharelib +# ------------------------------------------- +pcm_hook_type.MyHookPlugin { + install "AlsaInstallHook" + lib "/home/fulup/Workspace/AGL-AppFW/audio-bindings-dev/build/Alsa-Plugin/Alsa-Policy-Hook/policy_hook_cb.so" +} + + +# Define a HookedPCM that point to Hook_type sharelib +# ---------------------------------------------------- +pcm.MyNavigationHook { + type hooks + slave.pcm "MyMixerPCM" + # Defined used hook sharelib and provide arguments/config to install func + hooks.0 { + type "MyHookPlugin" + hook_args { + verbose true # print few log messages (default false); + + # Every Call should return OK in order PCM to open (default timeout 100ms) + uri "ws://localhost:1234/api?token=audio-agent-token&uuid=audio-agent-session" + request { + # Request autorisation to write on navigation + RequestNavigation { + api "control" + verb "dispatch" + query "{'target':'navigation', 'args':{'device':'Jabra SOLEMATE v1.34.0'}}" + } + } + # map event reception to self generated signal + event { + pause 30 + resume 31 + stop 3 + } + } + } +} + +# If hardware does not support mixer emulate it with softvol +# ----------------------------------------------------------- +pcm.MyMultimediaPCM { + type softvol + + # Point Slave on HOOK for policies control + slave.pcm "MyNavigationHook" + + # resolution=HAL(valMax+1) (default=256) + resolution 256 + + # name should match with HAL but do not set card=xx + control.name "Playback Navigation" + + # Make this plugin visible from aplay -L + hint { + show on + description "Navigation SolftVol PCM" + } +} + +# If hardware does not support mixer emulate it with softvol +# ----------------------------------------------------------- +pcm.MyNavPCM { + type softvol + + # Point Slave on HOOK for policies control + slave.pcm "MyNavigationHook" + + # resolution=HAL(valMax+1) (default=256) + resolution 256 + + # name should match with HAL but do not set card=xx + control.name "Playback Navigation" + + # Make this plugin visible from aplay -L + hint { + show on + description "Navigation SolftVol PCM" + } +} + +# If hardware does not support mixer emulate it with softvol +# ----------------------------------------------------------- +pcm.MyAlarmPCM { + type softvol + + # Point Slave on HOOK for policies control + slave.pcm "MyNavigationHook" + + # resolution=HAL(valMax+1) (default=256) + resolution 256 + + # name should match with HAL but do not set card=xx + control.name "Playback Navigation" + + # Make this plugin visible from aplay -L + hint { + show on + description "Navigation SolftVol PCM" + } +} + diff --git a/conf.d/project/alsa.d/ucm.sample/HDA Intel PCH.conf b/conf.d/project/alsa.d/ucm.sample/HDA Intel PCH.conf new file mode 100644 index 0000000..f6608a0 --- /dev/null +++ b/conf.d/project/alsa.d/ucm.sample/HDA Intel PCH.conf @@ -0,0 +1,6 @@ +Comment "Leon internal card" + +SectionUseCase."HiFi" { + File "HiFi.conf" + Comment "Default" +} diff --git a/conf.d/project/alsa.d/ucm.sample/HiFi.conf b/conf.d/project/alsa.d/ucm.sample/HiFi.conf new file mode 100644 index 0000000..9a53c8c --- /dev/null +++ b/conf.d/project/alsa.d/ucm.sample/HiFi.conf @@ -0,0 +1,84 @@ +SectionVerb { + EnableSequence [ + cdev "hw:PCH" + + cset "name='Master Playback Switch' on" + cset "name='Headphone Playback Switch' off" + cset "name='Speaker Playback Switch' on" + + cset "name='Capture Switch' on" + cset "name='Capture Volume' 39" + cset "name='Mic Boost Volume' 2" + cset "name='Internal Mic Boost Volume' 0" + #cset "name='Capture Source' 0" + ] + DisableSequence [ + ] + Value { + TQ "Music" + OutputDspName "speaker_eq" + PlaybackPCM "hw:PCH,0" + } +} + +SectionDevice."Headphone".0 { + Value { + JackName "Headphone Jack" + OutputDspName "Jheadphone" + } + EnableSequence [ + cdev "hw:PCH" + + cset "name='Speaker Playback Switch' off" + cset "name='Headphone Playback Switch' on" + ] + DisableSequence [ + cdev "hw:PCH" + + cset "name='Headphone Playback Switch' off" + cset "name='Speaker Playback Switch' on" + ] +} + +SectionDevice."Mic".0 { + Value { + JackName "Mic Jack" + } + EnableSequence [ + cdev "hw:PCH" + + #cset "name='Capture Source' 1" + ] + DisableSequence [ + cdev "hw:PCH" + + cset "name='Capture Source' 0" + ] +} + +SectionModifier."RecordMedia".0 { + SupportedDevice [ + "Headphone" + ] + EnableSequence [ + cdev "hw:PCH" + ] + + DisableSequence [ + cdev "hw:PCH" + ] + + TransitionSequence."ToModifierName" [ + cdev "hw:PCH" + ] + + # Optional TQ and ALSA PCMs + Value { + TQ Voice + CapturePCM "hw:1" + PlaybackVolume "name='Master Playback Volume',index=2" + PlaybackSwitch "name='Master Playback Switch',index=2" + } + +} + diff --git a/conf.d/project/alsa.d/ucm.sample/README b/conf.d/project/alsa.d/ucm.sample/README new file mode 100644 index 0000000..e7f08ae --- /dev/null +++ b/conf.d/project/alsa.d/ucm.sample/README @@ -0,0 +1,2 @@ +Should match sound card name ex: "HDA Intel PCH" +cp -r . /usr/share/alsa/ucm diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..cbfe5dc --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,52 @@ +########################################################################### +# Copyright 2017 Audiokinetic.com +# +# author: Francois Thibault <fthibault@audiokinetic.com> +# +# 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. +########################################################################### +# Generate API-v2 hat from OpenAPI json definition +macro(SET_TARGET_GENSKEL TARGET_NAME API_DEF_NAME) + add_custom_command(OUTPUT ${API_DEF_NAME}.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${API_DEF_NAME}.json + COMMAND afb-genskel ${API_DEF_NAME}.json >${API_DEF_NAME}.h + ) + add_custom_target(${API_DEF_NAME}_OPENAPI DEPENDS ${API_DEF_NAME}.h) + add_dependencies(${TARGET_NAME} ${API_DEF_NAME}_OPENAPI) + +endmacro(SET_TARGET_GENSKEL) + +# Add target to project dependency list +PROJECT_TARGET_ADD(audiohighlevel-afb) + + # Define project Targets + ADD_LIBRARY(${TARGET_NAME} MODULE ahl-binding.c ahl-deviceenum.c) + + # Generate API-v2 hat from OpenAPI json definition + SET_TARGET_GENSKEL(${TARGET_NAME} ahl-apidef) + + # Binder exposes a unique public entry point + SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES + PREFIX "afb-" + LABELS "BINDING" + LINK_FLAGS ${BINDINGS_LINK_FLAG} + OUTPUT_NAME ${TARGET_NAME} + ) + + # Library dependencies (include updates automatically) + TARGET_LINK_LIBRARIES(${TARGET_NAME} + afb-utilities + ${link_libraries} + ) + diff --git a/src/ahl-apidef.h b/src/ahl-apidef.h new file mode 100644 index 0000000..fa78681 --- /dev/null +++ b/src/ahl-apidef.h @@ -0,0 +1,297 @@ + +static const char _afb_description_v2_audiohl[] = + "{\"openapi\":\"3.0.0\",\"$schema\":\"http:iot.bzh/download/openapi/schem" + "a-3.0/default-schema.json\",\"info\":{\"description\":\"Audio high level" + " API for AGL applications\",\"title\":\"audiohighlevel\",\"version\":\"1" + ".0\",\"x-binding-c-generator\":{\"api\":\"audiohl\",\"version\":2,\"pref" + "ix\":\"audiohlapi_\",\"postfix\":\"\",\"start\":null,\"onevent\":null,\"" + "init\":\"AhlBindingInit\",\"scope\":\"\",\"private\":false}},\"servers\"" + ":[{\"url\":\"ws://{host}:{port}/api/audiohl\",\"description\":\"Audio hi" + "gh level API for AGL applications.\",\"variables\":{\"host\":{\"default\"" + ":\"localhost\"},\"port\":{\"default\":\"1234\"}},\"x-afb-events\":[{\"$r" + "ef\":\"#/components/schemas/afb-event\"}]}],\"components\":{\"schemas\":" + "{\"afb-reply\":{\"$ref\":\"#/components/schemas/afb-reply-v2\"},\"afb-ev" + "ent\":{\"$ref\":\"#/components/schemas/afb-event-v2\"},\"afb-reply-v2\":" + "{\"title\":\"Generic response.\",\"type\":\"object\",\"required\":[\"jty" + "pe\",\"request\"],\"properties\":{\"jtype\":{\"type\":\"string\",\"const" + "\":\"afb-reply\"},\"request\":{\"type\":\"object\",\"required\":[\"statu" + "s\"],\"properties\":{\"status\":{\"type\":\"string\"},\"info\":{\"type\"" + ":\"string\"},\"token\":{\"type\":\"string\"},\"uuid\":{\"type\":\"string" + "\"},\"reqid\":{\"type\":\"string\"}}},\"response\":{\"type\":\"object\"}" + "}},\"afb-event-v2\":{\"type\":\"object\",\"required\":[\"jtype\",\"event" + "\"],\"properties\":{\"jtype\":{\"type\":\"string\",\"const\":\"afb-event" + "\"},\"event\":{\"type\":\"string\"},\"data\":{\"type\":\"object\"}}},\"e" + "ndpoint_info\":{\"type\":\"object\",\"required\":[\"endpoint_id\",\"type" + "\",\"name\"],\"properties\":{\"endpoint_id\":{\"type\":\"int\"},\"type\"" + ":{\"type\":\"enum\"},\"name\":{\"type\":\"string\"}}},\"stream_info\":{\"" + "type\":\"object\",\"required\":[\"stream_id\",\"pcm_name\",\"name\"],\"p" + "roperties\":{\"stream_id\":{\"type\":\"int\"},\"pcm_name\":{\"type\":\"s" + "tring\"},\"$ref\":\"#/components/schemas/endpoint_info\"}},\"routing_inf" + "o\":{\"type\":\"object\",\"required\":[\"routing_id\",\"source_id\",\"si" + "nk_id\"],\"properties\":{\"routing_id\":{\"type\":\"int\"},\"source_id\"" + ":{\"type\":\"int\"},\"sink_id\":{\"type\":\"int\"}}}},\"x-permissions\":" + "{\"streamcontrol\":{\"permission\":\"urn:AGL:permission:audio:public:str" + "eamcontrol\"},\"routingcontrol\":{\"permission\":\"urn:AGL:permission:au" + "dio:public:routingcontrol\"},\"soundevent\":{\"permission\":\"urn:AGL:pe" + "rmission:audio:public:soundevent\"}},\"responses\":{\"200\":{\"descripti" + "on\":\"A complex object array response\",\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/afb-reply\"}}}},\"400\":" + "{\"description\":\"Invalid arguments\"}}},\"paths\":{\"/get_sources\":{\"" + "description\":\"Retrieve array of available audio sources\",\"get\":{\"p" + "arameters\":[{\"in\":\"query\",\"name\":\"audio_role\",\"required\":fals" + "e,\"schema\":{\"type\":\"enum\"}}],\"responses\":{\"200\":{\"$ref\":\"#/" + "components/responses/200\",\"response\":{\"description\":\"Array of endp" + "oint info structures\",\"type\":\"array\",\"items\":{\"$ref\":\"#/compon" + "ents/schemas/endpoint_info\"}}},\"400\":{\"$ref\":\"#/components/respons" + "es/400\"}}}},\"/get_sinks\":{\"description\":\"Retrieve array of availab" + "le audio sinks\",\"get\":{\"parameters\":[{\"in\":\"query\",\"name\":\"a" + "udio_role\",\"required\":false,\"schema\":{\"type\":\"enum\"}}],\"respon" + "ses\":{\"200\":{\"$ref\":\"#/components/responses/200\",\"response\":{\"" + "description\":\"Array of endpoint info structures\",\"type\":\"array\",\"" + "items\":{\"$ref\":\"#/components/schemas/endpoint_info\"}}},\"400\":{\"$" + "ref\":\"#/components/responses/400\"}}}},\"/stream_open\":{\"description" + "\":\"Request opening a stream\",\"get\":{\"x-permissions\":{\"$ref\":\"#" + "/components/x-permissions/streamcontrol\"},\"parameters\":[{\"in\":\"que" + "ry\",\"name\":\"audio_role\",\"required\":true,\"schema\":{\"type\":\"en" + "um\"}},{\"in\":\"query\",\"name\":\"endpoint_type\",\"required\":true,\"" + "schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoint_id\"" + ",\"required\":false,\"schema\":{\"type\":\"int\"}}],\"responses\":{\"200" + "\":{\"$ref\":\"#/components/responses/200\",\"response\":{\"description\"" + ":\"Stream information structure\",\"$ref\":\"#/components/schemas/stream" + "_info\"}},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/stream" + "_close\":{\"description\":\"Request closing a stream\",\"get\":{\"x-perm" + "issions\":{\"$ref\":\"#/components/x-permissions/streamcontrol\"},\"para" + "meters\":[{\"in\":\"query\",\"name\":\"stream_id\",\"required\":true,\"s" + "chema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/compon" + "ents/responses/200\"},\"400\":{\"$ref\":\"#/components/responses/400\"}}" + "}},\"/get_available_routings\":{\"description\":\"Retrieve array of avai" + "lable routing info structures\",\"get\":{\"parameters\":[{\"in\":\"query" + "\",\"name\":\"audio_role\",\"required\":false,\"schema\":{\"type\":\"enu" + "m\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\",\"" + "response\":{\"description\":\"Array of routing info structures\",\"type\"" + ":\"array\",\"items\":{\"description\":\"Routing info structure {routingI" + "D, sourceID, sinkID }\",\"type\":\"object\"}}},\"400\":{\"$ref\":\"#/com" + "ponents/responses/400\"}}}},\"/add_routing\":{\"description\":\"Request " + "a routing connection between available devices\",\"get\":{\"x-permission" + "s\":{\"$ref\":\"#/components/x-permissions/routingcontrol\"},\"parameter" + "s\":[{\"in\":\"query\",\"name\":\"audio_role\",\"required\":true,\"schem" + "a\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"routing_id\",\"req" + "uired\":false,\"schema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"" + "$ref\":\"#/components/responses/200\",\"response\":{\"description\":\"Ro" + "uting information structure\",\"$ref\":\"#/components/schemas/routing_in" + "fo\"}},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/remove_ro" + "uting\":{\"description\":\"Request to remove a routing connection betwee" + "n devices\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permis" + "sions/routingcontrol\"},\"parameters\":[{\"in\":\"query\",\"name\":\"rou" + "ting_id\",\"required\":true,\"schema\":{\"type\":\"int\"}}],\"responses\"" + ":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"" + "#/components/responses/400\"}}}},\"/set_endpoint_volume\":{\"description" + "\":\"Set endpoint volume\",\"get\":{\"x-permissions\":{\"$ref\":\"#/comp" + "onents/x-permissions/streamcontrol\"},\"parameters\":[{\"in\":\"query\"," + "\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"enum" + "\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":true,\"sche" + "ma\":{\"type\":\"int\"}},{\"in\":\"query\",\"name\":\"volume\",\"require" + "d\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"" + "ramp_time_ms\",\"required\":false,\"schema\":{\"type\":\"int\"}}],\"resp" + "onses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$r" + "ef\":\"#/components/responses/400\"}}}},\"/get_endpoint_volume\":{\"desc" + "ription\":\"Get endpoint volume\",\"get\":{\"parameters\":[{\"in\":\"que" + "ry\",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"type\":\"" + "enum\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\":true,\"" + "schema\":{\"type\":\"int\"}}],\"responses\":{\"200\":{\"$ref\":\"#/compo" + "nents/responses/200\",\"response\":{\"description\":\"Endpoint volume va" + "lue\",\"type\":\"double\"}},\"400\":{\"$ref\":\"#/components/responses/4" + "00\"}}}},\"/set_endpoint_property\":{\"description\":\"Set endpoint prop" + "erty value\",\"get\":{\"x-permissions\":{\"$ref\":\"#/components/x-permi" + "ssions/streamcontrol\"},\"parameters\":[{\"in\":\"query\",\"name\":\"end" + "point_type\",\"required\":true,\"schema\":{\"type\":\"enum\"}},{\"in\":\"" + "query\",\"name\":\"endpoint_id\",\"required\":false,\"schema\":{\"type\"" + ":\"int\"}},{\"in\":\"query\",\"name\":\"property_name\",\"required\":tru" + "e,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"value\"" + ",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"" + "name\":\"ramp_time_ms\",\"required\":false,\"schema\":{\"type\":\"int\"}" + "}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"},\"40" + "0\":{\"$ref\":\"#/components/responses/400\"}}}},\"/get_endpoint_propert" + "y\":{\"description\":\"Get endpoint property value\",\"get\":{\"paramete" + "rs\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"required\":true,\"s" + "chema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoint_id\"," + "\"required\":false,\"schema\":{\"type\":\"int\"}},{\"in\":\"query\",\"na" + "me\":\"property_name\",\"required\":true,\"schema\":{\"type\":\"string\"" + "}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\",\"re" + "sponse\":{\"description\":\"Property value\",\"type\":\"double\"}},\"400" + "\":{\"$ref\":\"#/components/responses/400\"}}}},\"/set_endpoint_state\":" + "{\"description\":\"Set endpoint state\",\"get\":{\"x-permissions\":{\"$r" + "ef\":\"#/components/x-permissions/streamcontrol\"},\"parameters\":[{\"in" + "\":\"query\",\"name\":\"endpoint_type\",\"required\":true,\"schema\":{\"" + "type\":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoint_id\",\"required\"" + ":true,\"schema\":{\"type\":\"int\"}},{\"in\":\"query\",\"name\":\"state_" + "name\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"in\":\"que" + "ry\",\"name\":\"state_value\",\"required\":true,\"schema\":{\"type\":\"s" + "tring\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200" + "\"},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/get_endpoint" + "_state\":{\"description\":\"Get endpoint state value\",\"get\":{\"parame" + "ters\":[{\"in\":\"query\",\"name\":\"endpoint_type\",\"required\":true,\"" + "schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"endpoint_id\"" + ",\"required\":true,\"schema\":{\"type\":\"int\"}},{\"in\":\"query\",\"na" + "me\":\"state_name\",\"required\":true,\"schema\":{\"type\":\"string\"}}]" + ",\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\",\"respo" + "nse\":{\"description\":\"Endpoint state value\",\"type\":\"string\"}},\"" + "400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/post_sound_event\"" + ":{\"description\":\"Post sound event\",\"get\":{\"x-permissions\":{\"$re" + "f\":\"#/components/x-permissions/soundevent\"},\"parameters\":[{\"in\":\"" + "query\",\"name\":\"event_name\",\"required\":true,\"schema\":{\"type\":\"" + "string\"}},{\"in\":\"query\",\"name\":\"audio_role\",\"required\":false," + "\"schema\":{\"type\":\"enum\"}},{\"in\":\"query\",\"name\":\"media_name\"" + ",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"in\":\"query\"," + "\"name\":\"audio_context\",\"required\":false,\"schema\":{\"type\":\"obj" + "ect\"}}],\"responses\":{\"200\":{\"$ref\":\"#/components/responses/200\"" + "},\"400\":{\"$ref\":\"#/components/responses/400\"}}}},\"/subscribe\":{\"" + "description\":\"Subscribe to audio high level events\",\"get\":{\"parame" + "ters\":[{\"in\":\"query\",\"name\":\"events\",\"required\":true,\"schema" + "\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}}],\"responses\":" + "{\"200\":{\"$ref\":\"#/components/responses/200\"},\"400\":{\"$ref\":\"#" + "/components/responses/400\"}}}}}}" +; + +static const struct afb_auth _afb_auths_v2_audiohl[] = { + { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:streamcontrol" }, + { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:routingcontrol" }, + { .type = afb_auth_Permission, .text = "urn:AGL:permission:audio:public:soundevent" } +}; + + void audiohlapi_get_sources(struct afb_req req); + void audiohlapi_get_sinks(struct afb_req req); + void audiohlapi_stream_open(struct afb_req req); + void audiohlapi_stream_close(struct afb_req req); + void audiohlapi_get_available_routings(struct afb_req req); + void audiohlapi_add_routing(struct afb_req req); + void audiohlapi_remove_routing(struct afb_req req); + void audiohlapi_set_endpoint_volume(struct afb_req req); + void audiohlapi_get_endpoint_volume(struct afb_req req); + void audiohlapi_set_endpoint_property(struct afb_req req); + void audiohlapi_get_endpoint_property(struct afb_req req); + void audiohlapi_set_endpoint_state(struct afb_req req); + void audiohlapi_get_endpoint_state(struct afb_req req); + void audiohlapi_post_sound_event(struct afb_req req); + void audiohlapi_subscribe(struct afb_req req); + +static const struct afb_verb_v2 _afb_verbs_v2_audiohl[] = { + { + .verb = "get_sources", + .callback = audiohlapi_get_sources, + .auth = NULL, + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "get_sinks", + .callback = audiohlapi_get_sinks, + .auth = NULL, + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "stream_open", + .callback = audiohlapi_stream_open, + .auth = &_afb_auths_v2_audiohl[0], + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "stream_close", + .callback = audiohlapi_stream_close, + .auth = &_afb_auths_v2_audiohl[0], + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "get_available_routings", + .callback = audiohlapi_get_available_routings, + .auth = NULL, + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "add_routing", + .callback = audiohlapi_add_routing, + .auth = &_afb_auths_v2_audiohl[1], + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "remove_routing", + .callback = audiohlapi_remove_routing, + .auth = &_afb_auths_v2_audiohl[1], + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "set_endpoint_volume", + .callback = audiohlapi_set_endpoint_volume, + .auth = &_afb_auths_v2_audiohl[0], + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "get_endpoint_volume", + .callback = audiohlapi_get_endpoint_volume, + .auth = NULL, + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "set_endpoint_property", + .callback = audiohlapi_set_endpoint_property, + .auth = &_afb_auths_v2_audiohl[0], + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "get_endpoint_property", + .callback = audiohlapi_get_endpoint_property, + .auth = NULL, + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "set_endpoint_state", + .callback = audiohlapi_set_endpoint_state, + .auth = &_afb_auths_v2_audiohl[0], + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "get_endpoint_state", + .callback = audiohlapi_get_endpoint_state, + .auth = NULL, + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "post_sound_event", + .callback = audiohlapi_post_sound_event, + .auth = &_afb_auths_v2_audiohl[2], + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { + .verb = "subscribe", + .callback = audiohlapi_subscribe, + .auth = NULL, + .info = NULL, + .session = AFB_SESSION_NONE_V2 + }, + { .verb = NULL } +}; + +const struct afb_binding_v2 afbBindingV2 = { + .api = "audiohl", + .specification = _afb_description_v2_audiohl, + .info = NULL, + .verbs = _afb_verbs_v2_audiohl, + .preinit = NULL, + .init = AhlBindingInit, + .onevent = NULL, + .noconcurrency = 0 +}; + diff --git a/src/ahl-apidef.json b/src/ahl-apidef.json new file mode 100644 index 0000000..cbe25a9 --- /dev/null +++ b/src/ahl-apidef.json @@ -0,0 +1,601 @@ +{ + "openapi": "3.0.0", + "$schema": "http:iot.bzh/download/openapi/schema-3.0/default-schema.json", + "info": { + "description": "Audio high level API for AGL applications", + "title": "audiohighlevel", + "version": "1.0", + "x-binding-c-generator": { + "api": "audiohl", + "version": 2, + "prefix": "audiohlapi_", + "postfix": "", + "start": null, + "onevent": null, + "init": "AhlBindingInit", + "scope": "", + "private": false + } + }, + "servers": [ + { + "url": "ws://{host}:{port}/api/audiohl", + "description": "Audio high level API for AGL applications.", + "variables": { + "host": { + "default": "localhost" + }, + "port": { + "default": "1234" + } + }, + "x-afb-events": [ + { + "$ref": "#/components/schemas/afb-event" + } + ] + } + ], + "components": { + "schemas": { + "afb-reply": { + "$ref": "#/components/schemas/afb-reply-v2" + }, + "afb-event": { + "$ref": "#/components/schemas/afb-event-v2" + }, + "afb-reply-v2": { + "title": "Generic response.", + "type": "object", + "required": ["jtype", "request"], + "properties": { + "jtype": { + "type": "string", + "const": "afb-reply" + }, + "request": { + "type": "object", + "required": ["status"], + "properties": { + "status": { + "type": "string" + }, + "info": { + "type": "string" + }, + "token": { + "type": "string" + }, + "uuid": { + "type": "string" + }, + "reqid": { + "type": "string" + } + } + }, + "response": { + "type": "object" + } + } + }, + "afb-event-v2": { + "type": "object", + "required": ["jtype", "event"], + "properties": { + "jtype": { + "type": "string", + "const": "afb-event" + }, + "event": { + "type": "string" + }, + "data": { + "type": "object" + } + } + }, + "endpoint_info": { + "type": "object", + "required": [ "endpoint_id", "type", "name" ], + "properties": { + "endpoint_id": { "type": "int" }, + "type": { "type": "enum" }, + "name": { "type": "string" } + } + }, + "stream_info": { + "type": "object", + "required": [ "stream_id", "pcm_name", "name" ], + "properties": { + "stream_id": { "type": "int" }, + "pcm_name": { "type": "string" }, + "$ref": "#/components/schemas/endpoint_info" + } + }, + "routing_info": { + "type": "object", + "required": [ "routing_id", "source_id", "sink_id" ], + "properties": { + "routing_id": { "type": "int" }, + "source_id": { "type": "int" }, + "sink_id": { "type": "int" } + } + } + }, + "x-permissions": { + "streamcontrol": { + "permission": "urn:AGL:permission:audio:public:streamcontrol" + }, + "routingcontrol": { + "permission": "urn:AGL:permission:audio:public:routingcontrol" + }, + "soundevent": { + "permission": "urn:AGL:permission:audio:public:soundevent" + } + }, + "responses": { + "200": { + "description": "A complex object array response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/afb-reply" + } + } + } + }, + "400": { + "description": "Invalid arguments" + } + } + }, + "paths": { + "/get_sources": { + "description": "Retrieve array of available audio sources", + "get": { + "parameters": [ + { + "in": "query", + "name": "audio_role", + "required": false, + "schema": { "type": "enum" } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/200", + "response": { + "description": "Array of endpoint info structures", + "type": "array", + "items": { "$ref": "#/components/schemas/endpoint_info"} + } + }, + "400": { "$ref": "#/components/responses/400" } + } + } + }, + "/get_sinks": { + "description": "Retrieve array of available audio sinks", + "get": { + "parameters": [ + { + "in": "query", + "name": "audio_role", + "required": false, + "schema": { "type": "enum" } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/200", + "response": { + "description": "Array of endpoint info structures", + "type": "array", + "items": { "$ref": "#/components/schemas/endpoint_info"} + } + }, + "400": { + "$ref": "#/components/responses/400" } + } + } + }, + "/stream_open": { + "description": "Request opening a stream", + "get": { + "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, + "parameters": [ + { + "in": "query", + "name": "audio_role", + "required": true, + "schema": { "type": "enum" } + }, + { + "in": "query", + "name": "endpoint_type", + "required": true, + "schema": { "type": "enum" } + }, + { + "in": "query", + "name": "endpoint_id", + "required": false, + "schema": { "type": "int" } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/200", + "response": { + "description": "Stream information structure", + "$ref": "#/components/schemas/stream_info" + } + }, + "400": { "$ref": "#/components/responses/400" } + } + } + }, + "/stream_close": { + "description": "Request closing a stream", + "get": { + "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, + "parameters": [ + { + "in": "query", + "name": "stream_id", + "required": true, + "schema": { "type": "int" } + } + ], + "responses": { + "200": { "$ref": "#/components/responses/200" }, + "400": { "$ref": "#/components/responses/400" } + } + } + }, + "/get_available_routings": { + "description": "Retrieve array of available routing info structures", + "get": { + "parameters": [ + { + "in": "query", + "name": "audio_role", + "required": false, + "schema": { "type": "enum" } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/200", + "response": { + "description": "Array of routing info structures", + "type": "array", + "items": { + "description": "Routing info structure {routingID, sourceID, sinkID }", + "type": "object" + } + } + }, + "400": { "$ref": "#/components/responses/400" } + } + } + }, + "/add_routing": { + "description": "Request a routing connection between available devices", + "get": { + "x-permissions": { "$ref": "#/components/x-permissions/routingcontrol" }, + "parameters": [ + { + "in": "query", + "name": "audio_role", + "required": true, + "schema": { "type": "enum" } + }, + { + "in": "query", + "name": "routing_id", + "required": false, + "schema": { "type": "int" } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/200", + "response": { + "description": "Routing information structure", + "$ref": "#/components/schemas/routing_info" + } + }, + "400": { "$ref": "#/components/responses/400" } + } + } + }, + "/remove_routing": { + "description": "Request to remove a routing connection between devices", + "get": { + "x-permissions": { "$ref": "#/components/x-permissions/routingcontrol" }, + "parameters": [ + { + "in": "query", + "name": "routing_id", + "required": true, + "schema": { "type": "int" } + } + ], + "responses": { + "200": { "$ref": "#/components/responses/200" }, + "400": { "$ref": "#/components/responses/400" } + } + } + }, + "/set_endpoint_volume": { + "description": "Set endpoint volume", + "get": { + "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, + "parameters": [ + { + "in": "query", + "name": "endpoint_type", + "required": true, + "schema": { "type": "enum" } + }, + { + "in": "query", + "name": "endpoint_id", + "required": true, + "schema": { "type": "int" } + }, + { + "in": "query", + "name": "volume", + "required": true, + "schema": { "type": "string" } + }, + { + "in": "query", + "name": "ramp_time_ms", + "required": false, + "schema": { "type": "int" } + } + ], + "responses": { + "200": { "$ref": "#/components/responses/200" }, + "400": { "$ref": "#/components/responses/400" } + } + } + }, + "/get_endpoint_volume": { + "description": "Get endpoint volume", + "get": { + "parameters": [ + { + "in": "query", + "name": "endpoint_type", + "required": true, + "schema": { "type": "enum" } + }, + { + "in": "query", + "name": "endpoint_id", + "required": true, + "schema": { "type": "int" } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/200", + "response": { + "description": "Endpoint volume value", + "type": "double" + } + }, + "400": { "$ref": "#/components/responses/400" } + } + } + }, + "/set_endpoint_property": { + "description": "Set endpoint property value", + "get": { + "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, + "parameters": [ + { + "in": "query", + "name": "endpoint_type", + "required": true, + "schema": { "type": "enum" } + }, + { + "in": "query", + "name": "endpoint_id", + "required": false, + "schema": { "type": "int" } + }, + { + "in": "query", + "name": "property_name", + "required": true, + "schema": { "type": "string" } + }, + { + "in": "query", + "name": "value", + "required": true, + "schema": { "type": "string" } + }, + { + "in": "query", + "name": "ramp_time_ms", + "required": false, + "schema": { "type": "int" } + } + ], + "responses": { + "200": { "$ref": "#/components/responses/200" }, + "400": { "$ref": "#/components/responses/400" } + } + } + }, + "/get_endpoint_property": { + "description": "Get endpoint property value", + "get": { + "parameters": [ + { + "in": "query", + "name": "endpoint_type", + "required": true, + "schema": { "type": "enum" } + }, + { + "in": "query", + "name": "endpoint_id", + "required": false, + "schema": { "type": "int" } + }, + { + "in": "query", + "name": "property_name", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/200", + "response": { + "description": "Property value", + "type": "double" + } + }, + "400": { "$ref": "#/components/responses/400" } + } + } + }, + "/set_endpoint_state": { + "description": "Set endpoint state", + "get": { + "x-permissions": { "$ref": "#/components/x-permissions/streamcontrol" }, + "parameters": [ + { + "in": "query", + "name": "endpoint_type", + "required": true, + "schema": { "type": "enum" } + }, + { + "in": "query", + "name": "endpoint_id", + "required": true, + "schema": { "type": "int" } + }, + { + "in": "query", + "name": "state_name", + "required": true, + "schema": { "type": "string" } + }, + { + "in": "query", + "name": "state_value", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { "$ref": "#/components/responses/200" }, + "400": { "$ref": "#/components/responses/400" } + } + } + }, + "/get_endpoint_state": { + "description": "Get endpoint state value", + "get": { + "parameters": [ + { + "in": "query", + "name": "endpoint_type", + "required": true, + "schema": { "type": "enum" } + }, + { + "in": "query", + "name": "endpoint_id", + "required": true, + "schema": { "type": "int" } + }, + { + "in": "query", + "name": "state_name", + "required": true, + "schema": { "type": "string" } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/200", + "response": { + "description": "Endpoint state value", + "type": "string" + } + }, + "400": { "$ref": "#/components/responses/400" } + } + } + }, + "/post_sound_event": { + "description": "Post sound event", + "get": { + "x-permissions": { "$ref": "#/components/x-permissions/soundevent" }, + "parameters": [ + { + "in": "query", + "name": "event_name", + "required": true, + "schema": { "type": "string" } + }, + { + "in": "query", + "name": "audio_role", + "required": false, + "schema": { "type": "enum" } + }, + { + "in": "query", + "name": "media_name", + "required": false, + "schema": { "type": "string"} + }, + { + "in": "query", + "name": "audio_context", + "required": false, + "schema": { "type": "object" } + } + ], + "responses": { + "200": { "$ref": "#/components/responses/200" }, + "400": { "$ref": "#/components/responses/400" } + } + } + }, + "/subscribe": { + "description": "Subscribe to audio high level events", + "get": { + "parameters": [ + { + "in": "query", + "name": "events", + "required": true, + "schema": { "type": "array", + "items": { "type": "string" } + } + } + ], + "responses": { + "200": { "$ref": "#/components/responses/200" }, + "400": { "$ref": "#/components/responses/400" } + } + } + } + } +} diff --git a/src/ahl-binding.c b/src/ahl-binding.c new file mode 100644 index 0000000..99236bd --- /dev/null +++ b/src/ahl-binding.c @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2017 "Audiokinetic Inc" + * Author Francois Thibault <fthibault@audiokinetic.com> + * + * 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 +#include <stdio.h> +#include <string.h> +#include <time.h> + +#include "ahl-binding.h" +#include "ahl-apidef.h" // Generated from JSON OpenAPI +#include "wrap-json.h" + +// TODO: json_object_put to free JSON objects? potential leaks currently + +// Helper macros/func for packaging JSON objects from C structures + +#define EndpointInfoStructToJSON(__JSON_OBJECT__, __ENDPOINTINFOSTRUCT__) \ + wrap_json_pack(&__JSON_OBJECT__, "{s:i,s:i,s:s}", "endpoint_id", __ENDPOINTINFOSTRUCT__.endpoint_id, "type", __ENDPOINTINFOSTRUCT__.type, "name", __ENDPOINTINFOSTRUCT__.name); + +#define RoutingInfoStructToJSON(__JSON_OBJECT__, __ROUTINGINFOSTRUCT__) \ + wrap_json_pack(&__JSON_OBJECT__, "{s:i,s:i,s:i}", "routing_id", __ROUTINGINFOSTRUCT__.routing_id, "source_id", __ROUTINGINFOSTRUCT__.source_id, "sink_id", __ROUTINGINFOSTRUCT__.sink_id); + +static void StreamInfoStructToJSON(json_object **streamInfoJ, StreamInfoT streamInfo) +{ + json_object *endpointInfoJ; + EndpointInfoStructToJSON(endpointInfoJ,streamInfo.endpoint_info); + wrap_json_pack(streamInfoJ, "{s:i,s:s}", "stream_id", streamInfo.stream_id, "pcm_name", streamInfo.pcm_name); + json_object_object_add(*streamInfoJ,"endpoint_info",endpointInfoJ); +} + +// Binding initialization +PUBLIC int AhlBindingInit() +{ + + int errcount = 0; + + // Initialize list of available sources/sinks using lower level services + errcount += EnumerateSources(); + errcount += EnumerateSinks(); + + // TODO: Register for device changes from lower level services + + // TODO: Parse high-level binding configuration file + + // TODO: Perform other binding initialization tasks + + AFB_DEBUG("Audio high-level Binding success errcount=%d", errcount); + return errcount; +} + +PUBLIC void audiohlapi_get_sources(struct afb_req req) +{ + json_object *sourcesJ = NULL; + json_object *sourceJ = NULL; + json_object *queryJ = NULL; + AudioRoleT audioRole = AUDIOROLE_MAXVALUE; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s?i}", "audio_role", &audioRole); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + + if (audioRole != AUDIOROLE_MAXVALUE) + { + AFB_DEBUG("Filtering according to specified audio role=%d", audioRole); + } + + // Fake run-time data for test purposes + EndpointInfoT sources[3]; + sources[0].endpoint_id = 0; + sources[0].type = 0; + sources[0].name = "Source0"; + sources[1].endpoint_id = 1; + sources[1].type = 1; + sources[1].name = "Source1"; + sources[2].endpoint_id = 2; + sources[2].type = 2; + sources[2].name = "Source2"; + + sourcesJ = json_object_new_array(); + for ( unsigned int i = 0 ; i < 3; i++) + { + EndpointInfoStructToJSON(sourceJ, sources[i]); + json_object_array_add(sourcesJ, sourceJ); + } + + afb_req_success(req, sourcesJ, "List of sources"); +} + +PUBLIC void audiohlapi_get_sinks(struct afb_req req) +{ + json_object *sinksJ = NULL; + json_object *sinkJ = NULL; + json_object *queryJ = NULL; + AudioRoleT audioRole = AUDIOROLE_MAXVALUE; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s?i}", "audio_role", &audioRole); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + + if (audioRole != AUDIOROLE_MAXVALUE) + { + AFB_DEBUG("Filtering according to specified audio role=%d", audioRole); + } + + // Fake run-time data for test purposes + EndpointInfoT sinks[3]; + sinks[0].endpoint_id = 0; + sinks[0].type = 0; + sinks[0].name = "Sink0"; + sinks[1].endpoint_id = 1; + sinks[1].type = 1; + sinks[1].name = "Sink1"; + sinks[2].endpoint_id = 2; + sinks[2].type = 2; + sinks[2].name = "Sink2"; + + sinksJ = json_object_new_array(); + for ( unsigned int i = 0 ; i < 3; i++) + { + EndpointInfoStructToJSON(sinkJ, sinks[i]); + json_object_array_add(sinksJ, sinkJ); + } + + afb_req_success(req, sinksJ, "List of sinks"); +} + +PUBLIC void audiohlapi_stream_open(struct afb_req req) +{ + json_object *streamInfoJ = NULL; + StreamInfoT streamInfo; + json_object *queryJ = NULL; + AudioRoleT audioRole; + EndpointTypeT endpointType; + endpointID_t endpointID = UNDEFINED_ID; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s?i}", "audio_role", &audioRole, "endpoint_type", &endpointType, "endpoint_id", &endpointID); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = audio_role:%d endpointType:%d endpointID:%d", audioRole,endpointType,endpointID); + + if (endpointID == UNDEFINED_ID) + { + // TODO: Go through configuration and available devices to find best device for specified role + endpointID = 2; + } + + // Fake run-time data for test purposes + streamInfo.stream_id = 12; + streamInfo.pcm_name = "plug:Entertainment"; + streamInfo.endpoint_info.endpoint_id = endpointID; + streamInfo.endpoint_info.type = endpointType; + streamInfo.endpoint_info.name = "MainSpeakers"; + + StreamInfoStructToJSON(&streamInfoJ,streamInfo); + + afb_req_success(req, streamInfoJ, "Stream info structure"); +} + +PUBLIC void audiohlapi_stream_close(struct afb_req req) +{ + json_object *queryJ = NULL; + streamID_t streamID = UNDEFINED_ID; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i}", "stream_id", &streamID); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = stream_id:%d", streamID); + + // TODO: Validate that the application ID from which the stream close is coming is the one that opened it, otherwise fail and do nothing + + afb_req_success(req, NULL, "Stream close completed"); +} + +// Routings +PUBLIC void audiohlapi_get_available_routings(struct afb_req req) +{ + json_object *routingsJ; + json_object *routingJ; + json_object *queryJ = NULL; + AudioRoleT audioRole = AUDIOROLE_MAXVALUE; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s?i}", "audio_role", &audioRole); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + + if (audioRole != AUDIOROLE_MAXVALUE) + { + AFB_DEBUG("Filtering according to specified audio role=%d", audioRole); + } + + // Fake run-time data for test purposes + RoutingInfoT routings[3]; + routings[0].source_id = 0; + routings[0].sink_id = 0; + routings[0].routing_id = 0; + routings[1].source_id = 1; + routings[1].sink_id = 1; + routings[1].routing_id = 1; + routings[2].source_id = 2; + routings[2].sink_id = 2; + routings[2].routing_id = 2; + + routingsJ = json_object_new_array(); + for (unsigned int i = 0; i < 3; i++) + { + RoutingInfoStructToJSON(routingJ, routings[i]); + json_object_array_add(routingsJ, routingJ); + } + + afb_req_success(req, routingsJ, "List of available routings"); +} + +PUBLIC void audiohlapi_add_routing(struct afb_req req) +{ + json_object *queryJ = NULL; + AudioRoleT audioRole = AUDIOROLE_MAXVALUE; + routingID_t routingID = UNDEFINED_ID; + json_object *routingJ = NULL; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s?i}", "audio_role", &audioRole,"routing_id",routingID); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = audio_role:%d routing_id:%d", audioRole,routingID); + + // Fake run-time data for test purposes + RoutingInfoT routingInfo; + routingInfo.routing_id = routingID; + routingInfo.source_id = 3; + routingInfo.sink_id = 4; + + RoutingInfoStructToJSON(routingJ,routingInfo); + + afb_req_success(req,routingJ, "Selected routing information"); +} + +PUBLIC void audiohlapi_remove_routing(struct afb_req req) +{ + json_object *queryJ = NULL; + routingID_t routingID = UNDEFINED_ID; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i}", "routing_id", &routingID); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = routing_id:%d", routingID); + + // TODO: Validate that the application ID from which the stream close is coming is the one that opened it, otherwise fail and do nothing + + afb_req_success(req, NULL, "Remove routing completed"); +} + +// Endpoints +PUBLIC void audiohlapi_set_endpoint_volume(struct afb_req req) +{ + json_object *queryJ = NULL; + endpointID_t endpointID = UNDEFINED_ID; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + char * volumeStr = NULL; + int rampTimeMS = 0; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s?i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"volume",&volumeStr,"ramp_time_ms",&rampTimeMS); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d volume:%s ramp_time_ms: %d", endpointType,endpointID,volumeStr,rampTimeMS); + + // TODO: Parse volume string to support increment/absolute/percent notation + + afb_req_success(req, NULL, "Set volume completed"); +} + +PUBLIC void audiohlapi_get_endpoint_volume(struct afb_req req) +{ + json_object *queryJ = NULL; + endpointID_t endpointID = UNDEFINED_ID; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + json_object *volumeJ; + double volume = 0.0; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d", endpointType,endpointID); + + volume = 87.0; // TODO: Get actual volume value + volumeJ = json_object_new_double(volume); + + afb_req_success(req, volumeJ, "Retrieved volume value"); +} + +PUBLIC void audiohlapi_set_endpoint_property(struct afb_req req) +{ + json_object *queryJ = NULL; + endpointID_t endpointID = UNDEFINED_ID; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + char * propertyName = NULL; + char * propValueStr = NULL; + int rampTimeMS = 0; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s:s,s?i}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName,"value",&propValueStr,"ramp_time_ms",&rampTimeMS); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s value:%s ramp_time_ms:%d", endpointType,endpointID,propertyName,propValueStr,rampTimeMS); + + // TODO: Parse property value string to support increment/absolute/percent notation + + afb_req_success(req, NULL, "Set property completed"); +} + +PUBLIC void audiohlapi_get_endpoint_property(struct afb_req req) +{ + json_object *queryJ = NULL; + endpointID_t endpointID = UNDEFINED_ID; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + char * propertyName = NULL; + json_object *propertyValJ; + double value = 0.0; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s?i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"property_name",&propertyName); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d property_name:%s", endpointType,endpointID,propertyName); + + value = 93.0; // TODO: Get actual property value + propertyValJ = json_object_new_double(value); + + afb_req_success(req, propertyValJ, "Retrieved property value"); +} + +PUBLIC void audiohlapi_set_endpoint_state(struct afb_req req) +{ + json_object *queryJ = NULL; + endpointID_t endpointID = UNDEFINED_ID; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + char * stateName = NULL; + char * stateValue = NULL; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"state_name",&stateName,"state_value",&stateValue); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d state_name:%s state_value:%s", endpointType,endpointID,stateName,stateValue); + + afb_req_success(req, NULL, "Set endpoint state completed"); +} + +PUBLIC void audiohlapi_get_endpoint_state(struct afb_req req) +{ + json_object *queryJ = NULL; + endpointID_t endpointID = UNDEFINED_ID; + EndpointTypeT endpointType = ENDPOINTTYPE_MAXVALUE; + json_object *stateValJ; + char * stateName = NULL; + char * stateValue = NULL; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:i,s:i,s:s}", "endpoint_type", &endpointType,"endpoint_id",&endpointID,"state_name",&stateName); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = endpoint_type:%d endpoint_id:%d state_name:%s", endpointType,endpointID,stateName); + + stateValJ = json_object_new_string(stateValue); + + afb_req_success(req, stateValJ, "Retrieved state value"); +} + +// Sound events +PUBLIC void audiohlapi_post_sound_event(struct afb_req req) +{ + json_object *queryJ = NULL; + char * eventName = NULL; + char * mediaName = NULL; + AudioRoleT audioRole; + json_object *audioContext = NULL; + + queryJ = afb_req_json(req); + int err = wrap_json_unpack(queryJ, "{s:s,s?i,s?s,s?o}", "event_name", &eventName,"audio_role",&audioRole,"media_name",&mediaName,"audio_context",&audioContext); + if (err) { + afb_req_fail_f(req, "Invalid arguments", "Args not a valid json object query=%s", json_object_get_string(queryJ)); + return; + } + AFB_DEBUG("Parsed input arguments = event_name:%s audio_role:%d media_name:%s", eventName,audioRole,mediaName); + + // TODO: Post sound event to rendering services + + afb_req_success(req, NULL, "Posted sound event"); + } + + +// Monitoring +PUBLIC void audiohlapi_subscribe(struct afb_req req) +{ + // json_object *queryJ = NULL; + + // queryJ = afb_req_json(req); + + // TODO: Iterate through array length, parsing the string value to actual events + // TODO: Subscribe to appropriate events from other services + + afb_req_success(req, NULL, "Subscribe to events finished"); +} diff --git a/src/ahl-binding.h b/src/ahl-binding.h new file mode 100644 index 0000000..7bd0653 --- /dev/null +++ b/src/ahl-binding.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017 "Audiokinetic Inc" + * Author Francois Thibault <fthibault@audiokinetic.com> + * + * 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. + */ + + +#ifndef AHL_BINDING_INCLUDE +#define AHL_BINDING_INCLUDE + +#define AFB_BINDING_VERSION 2 +#include <afb/afb-binding.h> +#include <json-c/json.h> + +#ifndef PUBLIC + #define PUBLIC +#endif + +#define UNDEFINED_ID -1 + +typedef int endpointID_t; +typedef int streamID_t; +typedef int routingID_t; + +typedef enum EndpointType { + ENDPOINTTYPE_SOURCE = 0, // source devices + ENDPOINTTYPE_SINK, // sink devices + ENDPOINTTYPE_MAXVALUE // Enum count, keep at the end +} EndpointTypeT; + +typedef enum AudioRole { + AUDIOROLE_WARNING = 0, // Safety-relevant or critical alerts/alarms + AUDIOROLE_GUIDANCE, // Important user information where user action is expected (e.g. navigation instruction) + AUDIOROLE_NOTIFICATION, // HMI or else notifications (e.g. touchscreen events, speech recognition on/off,...) + AUDIOROLE_COMMUNICATIONS, // Voice communications (e.g. handsfree, speech recognition) + AUDIOROLE_ENTERTAINMENT, // Multimedia content (e.g. tuner, media player, etc.) + AUDIOROLE_SYSTEM, // System level content + AUDIOROLE_DEFAULT, // No specific audio role (legacy applications) + AUDIOROLE_MAXVALUE // Enum count, keep at the end +} AudioRoleT; + +typedef enum AudioDeviceClass { + AUDIODEVICE_SPEAKERMAIN = 0, + AUDIODEVICE_SPEAKERHEADREST, + AUDIODEVICE_HEADSET, + AUDIODEVICE_HEADPHONE, + AUDIODEVICE_LINEOUT, + AUDIODEVICE_LINEIN, + AUDIODEVICE_BLUETOOTH, + AUDIODEVICE_HANDSET, + AUDIODEVICE_HDMI, + AUDIODEVICE_USB, + AUDIODEVICE_TONES, + AUDIODEVICE_VOICE, + AUDIODEVICE_PHONELINK, + AUDIODEVICE_DEFAULT, + AUDIODEVICE_MAXVALUE // Enum count, keep at the end +} AudioDeviceClassT; + +typedef struct EndpointInfo +{ + endpointID_t endpoint_id; + EndpointTypeT type; + char * name; + // TODO: Consider adding associated device class +} EndpointInfoT; + +typedef struct StreamInfo { + streamID_t stream_id; + char * pcm_name; + EndpointInfoT endpoint_info; +} StreamInfoT; + +typedef struct RoutingInfo { + routingID_t routing_id; + endpointID_t source_id; + endpointID_t sink_id; +} RoutingInfoT; + +PUBLIC int AhlBindingInit(); +// ahl-deviceenum.c +PUBLIC int EnumerateSources(); +PUBLIC int EnumerateSinks(); + +#endif diff --git a/src/ahl-deviceenum.c b/src/ahl-deviceenum.c new file mode 100644 index 0000000..6629de2 --- /dev/null +++ b/src/ahl-deviceenum.c @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 "Audiokinetic Inc" + * Author Francois Thibault <fthibault@audiokinetic.com> + * + * 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 +#include <stdio.h> +#include <string.h> +#include <time.h> + +#include "ahl-binding.h" + +PUBLIC int EnumerateSources() { + + // TODO: Use lower level services to build a list of available source devices + + AFB_DEBUG ("Audio high-level - Enumerate sources done"); + return 0; +} + +PUBLIC int EnumerateSinks() { + + // TODO: Use lower level services to build a list of available sink devices + + AFB_DEBUG ("Audio high-level - Enumerate sinks done"); + return 0; +} + |