aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Spradlin <alexaspradlin@google.com>2021-01-27 01:46:30 +0000
committerAlex Spradlin <alexaspradlin@google.com>2021-01-27 01:46:30 +0000
commit7d9f6c479efc3a595f9d57fa4c08f8e1d165b56f (patch)
tree2f6f91d78ab2c6db5d7a43d75a599d069fd5d023
parenta47703c0c5918e6207eeb4329a07ad33aac404e0 (diff)
parent7afe0ebde380ce4251ec54066a1f82abc7ed24d6 (diff)
downloadtinyalsa_new-7d9f6c479efc3a595f9d57fa4c08f8e1d165b56f.tar.gz
Merge branch upstream-master into tinyalsa am: 6e88308fb5 am: 7afe0ebde3
Change-Id: Ib0f88a23137b48b47ad672bb3c861a821f47b7bf
-rw-r--r--.gitignore23
-rw-r--r--.travis.yml23
-rw-r--r--Android.bp68
-rw-r--r--BUILD57
-rw-r--r--CMakeLists.txt97
l---------LICENSE1
-rw-r--r--METADATA3
-rw-r--r--MODULE_LICENSE_BSD0
-rw-r--r--Makefile40
-rw-r--r--NOTICE25
-rw-r--r--OWNERS2
-rw-r--r--README.md103
-rw-r--r--WORKSPACE7
-rw-r--r--debian/changelog54
-rw-r--r--debian/compat1
-rw-r--r--debian/control46
-rw-r--r--debian/copyright41
-rw-r--r--debian/libtinyalsa-dev.dirs.in2
-rw-r--r--debian/libtinyalsa-dev.install.in4
-rw-r--r--debian/libtinyalsa.dirs.in1
-rw-r--r--debian/libtinyalsa.install.in1
-rwxr-xr-xdebian/rules33
-rw-r--r--debian/tinyalsa.dirs2
-rw-r--r--debian/tinyalsa.install8
-rw-r--r--doxygen/Doxyfile2526
-rw-r--r--doxygen/Makefile29
-rw-r--r--doxygen/mainpage.h10
-rw-r--r--examples/Makefile23
-rw-r--r--examples/meson.build8
-rw-r--r--examples/pcm-readi.c83
-rw-r--r--examples/pcm-writei.c103
-rw-r--r--include/tinyalsa/asoundlib.h37
-rw-r--r--include/tinyalsa/attributes.h44
-rw-r--r--include/tinyalsa/interval.h54
-rw-r--r--include/tinyalsa/limits.h62
-rw-r--r--include/tinyalsa/meson.build10
-rw-r--r--include/tinyalsa/mixer.h164
-rw-r--r--include/tinyalsa/pcm.h373
-rw-r--r--include/tinyalsa/plugin.h269
-rw-r--r--include/tinyalsa/version.h58
-rw-r--r--meson.build38
-rw-r--r--meson_options.txt6
-rwxr-xr-xscripts/travis-build.sh22
-rwxr-xr-xscripts/version.sh260
-rw-r--r--src/Makefile69
-rw-r--r--src/limits.c12
-rw-r--r--src/mixer.c1294
-rw-r--r--src/mixer_hw.c121
-rw-r--r--src/mixer_io.h51
-rw-r--r--src/mixer_plugin.c475
-rw-r--r--src/pcm.c1770
-rw-r--r--src/pcm_hw.c142
-rw-r--r--src/pcm_io.h52
-rw-r--r--src/pcm_plugin.c778
-rw-r--r--src/snd_card_plugin.c149
-rw-r--r--src/snd_card_plugin.h74
-rw-r--r--tests/include/pcm_test_device.h54
-rw-r--r--tests/src/mixer_test.cc316
-rw-r--r--tests/src/pcm_in_test.cc114
-rw-r--r--tests/src/pcm_loopback_test.cc230
-rw-r--r--tests/src/pcm_out_test.cc216
-rw-r--r--tests/src/pcm_params_test.cc222
-rw-r--r--tests/src/pcm_test.cc103
-rw-r--r--utils/Makefile59
-rw-r--r--utils/meson.build9
-rw-r--r--utils/optparse.h403
-rw-r--r--utils/tinycap.187
-rw-r--r--utils/tinycap.c267
-rw-r--r--utils/tinymix.186
-rw-r--r--utils/tinymix.c580
-rw-r--r--utils/tinypcminfo.157
-rw-r--r--utils/tinypcminfo.c211
-rw-r--r--utils/tinyplay.194
-rw-r--r--utils/tinyplay.c435
-rw-r--r--utils/tinywavinfo.c224
75 files changed, 13575 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8773c34
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+/doxygen/html
+/doxygen/man
+
+/build
+
+*.o
+*.so
+*.so.*
+*.a
+
+/libs
+/obj
+
+/examples/audio.raw
+/examples/pcm-readi
+/examples/pcm-writei
+
+/utils/tinyplay
+/utils/tinycap
+/utils/tinymix
+/utils/tinypcminfo
+
+/bazel*
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..28667cb
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,23 @@
+os: linux
+dist: xenial
+sudo: false
+language: c
+compiler:
+ - gcc
+ - clang
+
+addons:
+ apt:
+ packages:
+ - doxygen
+ - graphviz
+ - python3-pip
+ - python3-setuptools
+
+before_script:
+ - pip3 install --upgrade pip
+ - pip3 install --user ninja
+ - pip3 install --user meson
+
+script:
+ - scripts/travis-build.sh
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..51025b8
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,68 @@
+cc_library {
+ name: "libtinyalsav2",
+ host_supported: true,
+ vendor_available: true,
+ srcs: [
+ "src/mixer.c",
+ "src/mixer_hw.c",
+ "src/mixer_plugin.c",
+ "src/pcm.c",
+ "src/pcm_hw.c",
+ "src/pcm_plugin.c",
+ "src/snd_card_plugin.c",
+ ],
+ cflags: ["-Werror", "-Wno-macro-redefined"],
+ export_include_dirs: ["include"],
+ local_include_dirs: ["include"],
+
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+
+ system_shared_libs: ["libc", "libdl"],
+
+ sanitize: {
+ integer_overflow: true,
+ misc_undefined: ["bounds"],
+ diag: {
+ integer_overflow: true,
+ misc_undefined: ["bounds"],
+ },
+ },
+}
+
+cc_binary {
+ name: "tinyplay2",
+ host_supported: true,
+ srcs: ["utils/tinyplay.c"],
+ shared_libs: ["libtinyalsav2"],
+ cflags: ["-Werror"],
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+}
+
+cc_binary {
+ name: "tinycap2",
+ srcs: ["utils/tinycap.c"],
+ shared_libs: ["libtinyalsav2"],
+ cflags: ["-Werror"],
+}
+
+cc_binary {
+ name: "tinymix2",
+ srcs: ["utils/tinymix.c"],
+ shared_libs: ["libtinyalsav2"],
+ cflags: ["-Werror", "-Wall"],
+}
+
+cc_binary {
+ name: "tinypcminfo2",
+ srcs: ["utils/tinypcminfo.c"],
+ shared_libs: ["libtinyalsav2"],
+ cflags: ["-Werror"],
+}
diff --git a/BUILD b/BUILD
new file mode 100644
index 0000000..dba4ab5
--- /dev/null
+++ b/BUILD
@@ -0,0 +1,57 @@
+# BUILD
+#
+# Copyright 2020, The Android Open Source Project
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of The Android Open Source Project nor the names of
+# its contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+# DAMAGE.
+#
+
+cc_library(
+ name = "tinyalsa",
+ srcs = glob(["src/*.c"]),
+ includes = ["include"],
+ hdrs = glob([
+ "include/**/*.h",
+ "src/*.h",
+ ]),
+ visibility = ["//visibility:public"],
+)
+
+cc_test(
+ name = "tinyalsa_tests",
+ srcs = glob([
+ "tests/src/*.cc",
+ "tests/include/*.h",
+ ]),
+ includes = ["tests/include"],
+ deps = [
+ "//:tinyalsa",
+ "@googletest//:gtest_main"
+ ],
+ linkopts = [
+ "-ldl",
+ ],
+ copts = [
+ "-std=c++17",
+ ],
+)
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..84de10b
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,97 @@
+cmake_minimum_required(VERSION 3.1)
+
+execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/scripts/version.sh -s print
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ OUTPUT_VARIABLE TINYALSA_VERSION)
+
+project("TinyALSA" VERSION ${TINYALSA_VERSION} LANGUAGES C)
+
+set(CMAKE_C_STANDARD 99)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+set(CMAKE_C_EXTENSIONS OFF)
+
+# Options
+option(BUILD_SHARED_LIBS "Build shared libraries" ON)
+option(TINYALSA_USES_PLUGINS "Whether or not to build with plugin support" OFF)
+option(TINYALSA_BUILD_EXAMPLES "Build examples" ON)
+option(TINYALSA_BUILD_UTILS "Build utility tools" ON)
+
+# Library
+add_library("tinyalsa"
+ "src/pcm.c"
+ "src/pcm_hw.c"
+ "src/pcm_plugin.c"
+ "src/snd_card_plugin.c"
+ "src/mixer.c"
+ "src/mixer_hw.c"
+ "src/mixer_plugin.c")
+
+set_property(TARGET "tinyalsa" PROPERTY PUBLIC_HEADER
+ "include/tinyalsa/attributes.h"
+ "include/tinyalsa/version.h"
+ "include/tinyalsa/asoundlib.h"
+ "include/tinyalsa/pcm.h"
+ "include/tinyalsa/plugin.h"
+ "include/tinyalsa/mixer.h")
+
+set_target_properties("tinyalsa" PROPERTIES
+ VERSION ${TinyALSA_VERSION}
+ SOVERSION ${TinyALSA_VERSION_MAJOR})
+
+target_include_directories("tinyalsa" PUBLIC
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+ $<INSTALL_INTERFACE:include>)
+target_compile_definitions("tinyalsa" PRIVATE
+ $<$<BOOL:${TINYALSA_USES_PLUGINS}>:TINYALSA_USES_PLUGINS>
+ PUBLIC _POSIX_C_SOURCE=200809L)
+target_link_libraries("tinyalsa" PUBLIC ${CMAKE_DL_LIBS})
+
+# Examples
+if(TINYALSA_BUILD_EXAMPLES)
+ set(TINYALSA_EXAMPLES pcm-readi pcm-writei)
+else()
+ set(TINYALSA_EXAMPLES)
+endif()
+
+foreach(EXAMPLE IN LISTS TINYALSA_EXAMPLES)
+ add_executable("${EXAMPLE}" "examples/${EXAMPLE}.c")
+ target_link_libraries("${EXAMPLE}" PRIVATE "tinyalsa")
+endforeach()
+
+# Utilities
+if(TINYALSA_BUILD_UTILS)
+ set(TINYALSA_UTILS tinyplay tinycap tinypcminfo tinymix tinywavinfo)
+else()
+ set(TINYALSA_UTILS)
+endif()
+
+foreach(UTIL IN LISTS TINYALSA_UTILS)
+ add_executable("${UTIL}" "utils/${UTIL}.c")
+ target_link_libraries("${UTIL}" PRIVATE "tinyalsa")
+endforeach()
+
+if(TINYALSA_BUILD_UTILS)
+ target_link_libraries("tinywavinfo" PRIVATE m)
+endif()
+
+# Add C warning flags
+include(CheckCCompilerFlag)
+foreach(FLAG IN ITEMS -Wall -Wextra -Wpedantic -Werror -Wfatal-errors)
+ string(TOUPPER "HAVE${FLAG}" HAVE_VAR)
+ string(REPLACE "-" "_" HAVE_VAR "${HAVE_VAR}")
+ check_c_compiler_flag("${FLAG}" "${HAVE_VAR}")
+ if("${${HAVE_VAR}}")
+ target_compile_options("tinyalsa" PRIVATE "${FLAG}")
+ foreach(UTIL IN LISTS TINYALSA_UTILS)
+ target_compile_options("${UTIL}" PRIVATE "${FLAG}")
+ endforeach()
+ endif()
+endforeach()
+
+# Install
+include(GNUInstallDirs)
+install(TARGETS "tinyalsa" ${TINYALSA_UTILS}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tinyalsa)
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..132d13f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+NOTICE \ No newline at end of file
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..d97975c
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+ license_type: NOTICE
+}
diff --git a/MODULE_LICENSE_BSD b/MODULE_LICENSE_BSD
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_BSD
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..5fabb31
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,40 @@
+export DESTDIR ?=
+export PREFIX ?= /usr/local
+
+export INCDIR ?= $(PREFIX)/include/tinyalsa
+export LIBDIR ?= $(PREFIX)/lib
+export BINDIR ?= $(PREFIX)/bin
+export MANDIR ?= $(PREFIX)/share/man
+
+export VERSIONSCRIPT = $(shell pwd)/scripts/version.sh
+
+export TINYALSA_VERSION_MAJOR = $(shell $(VERSIONSCRIPT) -s print major)
+export TINYALSA_VERSION = $(shell $(VERSIONSCRIPT) -s print )
+
+.PHONY: all
+all:
+ $(MAKE) -C src
+ $(MAKE) -C utils
+ $(MAKE) -C doxygen
+ $(MAKE) -C examples
+
+.PHONY: clean
+clean:
+ $(MAKE) -C src clean
+ $(MAKE) -C utils clean
+ $(MAKE) -C doxygen clean
+ $(MAKE) -C examples clean
+
+.PHONY: install
+install:
+ install -d $(DESTDIR)$(INCDIR)/
+ install include/tinyalsa/attributes.h $(DESTDIR)$(INCDIR)/
+ install include/tinyalsa/pcm.h $(DESTDIR)$(INCDIR)/
+ install include/tinyalsa/mixer.h $(DESTDIR)$(INCDIR)/
+ install include/tinyalsa/asoundlib.h $(DESTDIR)$(INCDIR)/
+ install include/tinyalsa/version.h $(DESTDIR)$(INCDIR)/
+ install include/tinyalsa/plugin.h $(DESTDIR)$(INCDIR)/
+ $(MAKE) -C src install
+ $(MAKE) -C utils install
+ $(MAKE) -C doxygen install
+
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..5debd99
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,25 @@
+Copyright 2011, The Android Open Source Project
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of The Android Open Source Project nor the names of
+ its contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..e1e3676
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+dvdli@google.com
+gkasten@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ed4203a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,103 @@
+TinyALSA
+========
+
+[![Build Status](https://travis-ci.org/tinyalsa/tinyalsa.svg?branch=master)](https://travis-ci.org/tinyalsa/tinyalsa)
+
+TinyALSA is a small library to interface with ALSA in the Linux kernel.
+
+The aims are:
+
+ - Provide a basic pcm and mixer API.
+ - If it's not absolutely needed, don't add it to the API.
+ - Avoid supporting complex and unnecessary operations, that could be
+ dealt with at a higher level.
+ - Provide comprehensive documentation.
+
+### Building
+
+TinyALSA supports these build systems:
+
+ - [CMake](https://en.wikipedia.org/wiki/CMake)
+ - [Make](https://en.wikipedia.org/wiki/Make_(software))
+ - [Meson](https://en.wikipedia.org/wiki/Meson_(software))
+ - [Soong](https://android.googlesource.com/platform/build/soong/+/refs/heads/master/README.md) for Android
+
+To build and install with Make, run the commands:
+
+```
+make
+sudo make install
+sudo ldconfig
+```
+
+### Installing
+
+TinyALSA is now available as a set of the following [Debian](https://en.wikipedia.org/wiki/Debian)
+packages from [launchpad](https://launchpad.net/~taylorcholberton/+archive/ubuntu/tinyalsa):
+
+| Package Name: | Description: |
+|-----------------|-----------------------------------------------------|
+| tinyalsa | Contains tinyplay, tinycap, tinymix and tinypcminfo |
+| libtinyalsa | Contains the shared library |
+| libtinyalsa-dev | Contains the static library and header files |
+
+To install these packages, run the commands:
+
+```
+sudo apt-add-repository ppa:taylorcholberton/tinyalsa
+sudo apt-get update
+sudo apt-get install tinyalsa
+sudo apt-get install libtinyalsa-dev
+```
+
+### Documentation
+
+Once installed, the man pages are available via:
+
+```
+man tinyplay
+man tinycap
+man tinymix
+man tinypcminfo
+man libtinyalsa-pcm
+man libtinyalsa-mixer
+```
+
+### Test
+
+To test libtinyalsa, please follow the instructions,
+
+#### Setup Bazel build environment
+
+Visit [here](https://docs.bazel.build/versions/3.7.0/install.html) to get more info to setup Bazel environment.
+
+#### Insert loopback devices
+
+The test program does pcm_* operations on loopback devices. You have to insert loopback devices after your system boots up.
+
+```
+sudo modprobe snd-aloop
+sudo chmod 777 /dev/snd/*
+```
+
+#### Run test program
+
+```
+bazel test //:tinyalsa_tests --test_output=all
+```
+
+The default playback device is hw:2,0 and the default capture device is hw:2,1. If your loopback devices are not hw:2,0 and hw:2,1, you can specify the loopback device.
+
+```
+bazel test //:tinyalsa_tests --test_output=all \
+ --copt=-DTEST_LOOPBACK_CARD=[loopback card] \
+ --copt=-DTEST_LOOPBACK_PLAYBACK_DEVICE=[loopback playback device] \
+ --copt=-DTEST_LOOPBACK_CAPTURE_DEVICE=[loopback capture device]
+```
+
+#### Generate coverage report
+
+```
+bazel coverage //:tinyalsa_tests --combined_report=lcov --test_output=all
+genhtml bazel-out/_coverage/_coverage_report.dat -o tinyalsa_tests_coverage
+```
diff --git a/WORKSPACE b/WORKSPACE
new file mode 100644
index 0000000..02b57bf
--- /dev/null
+++ b/WORKSPACE
@@ -0,0 +1,7 @@
+load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
+
+git_repository(
+ name = "googletest",
+ remote = "https://github.com/google/googletest",
+ branch = "master",
+)
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..637ce56
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,54 @@
+tinyalsa (2.0.0) xenial; urgency=medium
+
+ * Miscellaneous bugs fixed.
+ * PCM plugin support.
+ * Add CMake build support.
+ * Add meson build support.
+ * tinyplay can now read from stdin.
+ * Improved versioning support for library.
+ * Improvements to pcm actions (prepare at open time and after overrun, etc.)
+ * Improvements/fixes to pcm_get_htimestamp().
+ * Fixes for the mixer percent functions.
+
+ -- Simon Wilson <ksattic@gmail.com> Mon, 21 Sep 2020 11:27:00 -0600
+
+tinyalsa (1.1.1) xenial; urgency=medium
+
+ * Fixed some minor bugs
+ * Added some protection from integer overflow
+
+ -- Taylor Holberton <taylorcholberton@gmail.com> Tue, 23 May 2017 21:21:10 -0800
+
+tinyalsa (1.1.0) xenial; urgency=medium
+
+ * Finished most of the PCM and mixer API documentation
+ * Added const specifiers where necessary
+ * Added pcm_readi() and pcm_writei()
+ * Deprecated pcm_read() and pcm_write()
+ * Added mixer_get_num_ctls_by_name()
+ * Added pcm_get_channels(), pcm_get_rate() and pcm_get_format()
+ * Made libtinyalsa.so.x a symbol link, using libtinyalsa.so.x.y.z as library name
+ * Added long option names in tinyplay
+ * Using amixer-style interface for tinymix
+
+ -- Taylor Holberton <taylorcholberton@gmail.com> Thu, 01 Dec 2016 21:38:12 -0800
+
+tinyalsa (1.0.2) xenial; urgency=medium
+
+ * Removed install of libtinyalsa.so in package libtinyalsa
+
+ -- Taylor Holberton <taylorcholberton@gmail.com> Sun, 02 Oct 2016 13:46:33 -0400
+
+tinyalsa (1.0.1) xenial; urgency=medium
+
+ * Added man pages for tinycap, tinyplay, tinymix and tinypcminfo.
+ * Fixed overwrite of shared library in package libtinyalsa-dev
+
+ -- Taylor Holberton <taylorcholberton@gmail.com> Sun, 02 Oct 2016 12:24:28 -0400
+
+tinyalsa (1.0.0) xenial; urgency=medium
+
+ * Initial debian release.
+
+ -- Taylor Holberton <taylorcholberton@gmail.com> Sat, 01 Oct 2016 20:31:04 -0400
+
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..66ca6f5
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,46 @@
+Source: tinyalsa
+Section: sound
+Priority: optional
+Maintainer: Taylor Holberton <taylorcholberton@gmail.com>
+Build-Depends: debhelper (>=7.0.50), doxygen
+Vcs-Git: git://github.com/tinyalsa/tinyalsa
+Vcs-Browser: https://github.com/tinyalsa/tinyalsa
+Standards-Version: 3.9.7
+
+Package: tinyalsa
+Architecture: any
+Section: sound
+Depends: libtinyalsa (= ${binary:Version}),
+ ${misc:Depends},
+ ${shlibs:Depends}
+Description: A collection of small programs to interface with ALSA in the Linux kernel.
+ TinyALSA is a lightweight interface to ALSA in the Linux kernel.
+
+Package: libtinyalsa
+Architecture: any
+Multi-Arch: same
+Section: libs
+Depends: ${misc:Depends},
+ ${shlibs:Depends}
+Description: A small C library for interfacing with ALSA in the Linux kernel.
+ The TinyALSA library is a lightweight, bare metal version of the ALSA library.
+
+Package: libtinyalsa-dev
+Architecture: any
+Multi-Arch: same
+Section: libdevel
+Depends: libtinyalsa,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Description: Development files for the TinyALSA library.
+ The TinyALSA library is a lightweight, bare metal version of the ALSA library.
+
+Package: libtinyalsa-dbg
+Architecture: any
+Multi-Arch: same
+Section: debug
+Depends: libtinyalsa,
+ ${misc:Depends}
+Description: Debug files for the TinyALSA library.
+ The TinyALSA library is a lightweight, bare metal version of the ALSA library.
+
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..6ae2c37
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,41 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: tinyalsa
+Source: https://github.com/tinyalsa/tinyalsa
+
+Files: *
+Copyright: 2016 Simon Wilson
+License: BSD-3-Clause
+
+Files: debian/*
+Copyright: 2016 Taylor Holberton <taylorcholberton@gmail.com>
+License: BSD-3-Clause
+
+License: BSD-3-Clause
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the Android Open Source Project nor the names of
+ its contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HOLDERS OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Please also look if there are files or directories which have a
+# different copyright/license attached and list them here.
+# Please avoid picking licenses with terms that are more restrictive than the
+# packaged work, as it may make Debian's contributions unacceptable upstream.
diff --git a/debian/libtinyalsa-dev.dirs.in b/debian/libtinyalsa-dev.dirs.in
new file mode 100644
index 0000000..5c59953
--- /dev/null
+++ b/debian/libtinyalsa-dev.dirs.in
@@ -0,0 +1,2 @@
+/usr/lib/@DEB_HOST_MULTIARCH@
+/usr/include
diff --git a/debian/libtinyalsa-dev.install.in b/debian/libtinyalsa-dev.install.in
new file mode 100644
index 0000000..853d12c
--- /dev/null
+++ b/debian/libtinyalsa-dev.install.in
@@ -0,0 +1,4 @@
+debian/tmp/usr/include/* usr/include/
+debian/tmp/usr/lib/@DEB_HOST_MULTIARCH@/lib*.so usr/lib/@DEB_HOST_MULTIARCH@/
+debian/tmp/usr/lib/@DEB_HOST_MULTIARCH@/lib*.a usr/lib/@DEB_HOST_MULTIARCH@/
+debian/tmp/usr/share/man/man3 usr/share/man/
diff --git a/debian/libtinyalsa.dirs.in b/debian/libtinyalsa.dirs.in
new file mode 100644
index 0000000..6768aca
--- /dev/null
+++ b/debian/libtinyalsa.dirs.in
@@ -0,0 +1 @@
+/usr/lib/@DEB_HOST_MULTIARCH@
diff --git a/debian/libtinyalsa.install.in b/debian/libtinyalsa.install.in
new file mode 100644
index 0000000..a9ec3d3
--- /dev/null
+++ b/debian/libtinyalsa.install.in
@@ -0,0 +1 @@
+debian/tmp/usr/lib/@DEB_HOST_MULTIARCH@/*.so.* usr/lib/@DEB_HOST_MULTIARCH@/
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..7daeb27
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,33 @@
+#!/usr/bin/make -f
+# See debhelper(7) (uncomment to enable)
+# output every command that modifies files on the build system.
+#DH_VERBOSE = 1
+
+DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH)
+export DEB_HOST_MULTIARCH
+
+PREPROCESS_FILES := $(wildcard debian/*.in)
+
+$(PREPROCESS_FILES:.in=): %: %.in
+ sed 's,/@DEB_HOST_MULTIARCH@,$(DEB_HOST_MULTIARCH:%=/%),g' $< > $@
+
+.PHONY: override_dh_auto_clean
+override_dh_auto_clean:
+ dh_auto_clean
+ rm -rf $(PREPROCESS_FILES:.in=)
+
+.PHONY: override_dh_auto_install
+override_dh_auto_install: $(PREPROCESS_FILES:.in=)
+ dh_auto_install -- PREFIX=/usr
+
+.PHONY: override_dh_auto_test
+override_dh_auto_test:
+
+
+.PHONY: override_dh_strip
+override_dh_strip:
+ dh_strip --dbg-package=libtinyalsa-dbg
+
+%:
+ dh $@
+
diff --git a/debian/tinyalsa.dirs b/debian/tinyalsa.dirs
new file mode 100644
index 0000000..99b95ed
--- /dev/null
+++ b/debian/tinyalsa.dirs
@@ -0,0 +1,2 @@
+/usr/bin
+/usr/share
diff --git a/debian/tinyalsa.install b/debian/tinyalsa.install
new file mode 100644
index 0000000..438db01
--- /dev/null
+++ b/debian/tinyalsa.install
@@ -0,0 +1,8 @@
+debian/tmp/usr/bin/tinyplay usr/bin/
+debian/tmp/usr/bin/tinycap usr/bin/
+debian/tmp/usr/bin/tinymix usr/bin/
+debian/tmp/usr/bin/tinypcminfo usr/bin/
+debian/tmp/usr/share/man/man1/tinyplay.1 usr/share/man/man1/
+debian/tmp/usr/share/man/man1/tinycap.1 usr/share/man/man1/
+debian/tmp/usr/share/man/man1/tinymix.1 usr/share/man/man1/
+debian/tmp/usr/share/man/man1/tinypcminfo.1 usr/share/man/man1/
diff --git a/doxygen/Doxyfile b/doxygen/Doxyfile
new file mode 100644
index 0000000..a57ac00
--- /dev/null
+++ b/doxygen/Doxyfile
@@ -0,0 +1,2526 @@
+# Doxyfile 1.8.17
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME = "TinyALSA"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER = 1.1.0
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF = "A tiny library to interface with ALSA in the Linux kernel."
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY =
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE = English
+
+# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all generated output in the proper direction.
+# Possible values are: None, LTR, RTL and Context.
+# The default value is: None.
+
+OUTPUT_TEXT_DIRECTION = None
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
+# such as
+# /***************
+# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
+# Javadoc-style will behave just like regular comments and it will not be
+# interpreted by doxygen.
+# The default value is: NO.
+
+JAVADOC_BANNER = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines (in the resulting output). You can put ^^ in the value part of an
+# alias to insert a newline as if a physical newline was in the original file.
+# When you need a literal { or } or , in the value part of an alias you have to
+# escape them by means of a backslash (\), this can lead to conflicts with the
+# commands \{ and \} for these it is advised to use the version @{ and @} or use
+# a double escape (\\{ and \\})
+
+ALIASES =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
+# sources only. Doxygen will then generate output that is more tailored for that
+# language. For instance, namespaces will be presented as modules, types will be
+# separated into more groups, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_SLICE = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
+# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice,
+# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
+# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
+# tries to guess whether the code is fixed or free formatted code, this is the
+# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat
+# .inc files as Fortran files (default is PHP), and .f files as C (default is
+# Fortran), use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See https://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 5.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS = 5
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
+# methods of a class will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIV_VIRTUAL = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# declarations. If set to NO, these declarations will be included in the
+# documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# (including Cygwin) ands Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET = YES
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation. If
+# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC = YES
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT = "./mainpage.h" \
+ "../src" \
+ "../include/tinyalsa"
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
+# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen
+# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd,
+# *.vhdl, *.ucf, *.qsf and *.ice.
+
+FILE_PATTERNS = "*.c" \
+ "*.h"
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH = "../examples"
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# entity all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see https://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS = YES
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
+# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
+# cost of reduced performance. This can be particularly helpful with template
+# rich C++ code for which doxygen's built-in parser lacks the necessary type
+# information.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+# The default value is: NO.
+
+CLANG_ASSISTED_PARSING = NO
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_OPTIONS =
+
+# If clang assisted parsing is enabled you can provide the clang parser with the
+# path to the compilation database (see:
+# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) used when the files
+# were built. This is equivalent to specifying the "-p" option to a clang tool,
+# such as clang-check. These options will then be passed to the parser.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+
+CLANG_DATABASE_PATH =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP = NO
+
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via JavaScript. If disabled, the navigation index will
+# consists of multiple levels of tabs that are statically embedded in every HTML
+# page. Disable this option to support browsers that do not have JavaScript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_MENUS = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: https://developer.apple.com/xcode/), introduced with OSX
+# 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
+# genXcode/_index.html for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT = YES
+
+# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
+# to create new LaTeX commands to be used in formulas as building blocks. See
+# the section "Including formulas" for details.
+
+FORMULA_MACROFILE =
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# https://www.mathjax.org) which uses client side JavaScript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from https://www.mathjax.org before deployment.
+# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using JavaScript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: https://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: https://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when not enabling USE_PDFLATEX the default is latex when enabling
+# USE_PDFLATEX the default is pdflatex and when in the later case latex is
+# chosen this is overwritten by pdflatex. For specific output languages the
+# default can have been set differently, this depends on the implementation of
+# the output language.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# Note: This tag is used in the Makefile / make.bat.
+# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
+# (.tex).
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
+# generate index for LaTeX. In case there is no backslash (\) as first character
+# it will be automatically added in the LaTeX code.
+# Note: This tag is used in the generated output file (.tex).
+# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
+# The default value is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_MAKEINDEX_CMD = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP = NO
+
+# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
+# path from which the emoji images will be read. If a relative path is entered,
+# it will be relative to the LATEX_OUTPUT directory. If left blank the
+# LATEX_OUTPUT directory will be used.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EMOJI_DIRECTORY =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# configuration file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's configuration file. A template extensions file can be
+# generated using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING = YES
+
+# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
+# namespace members in file scope as well, matching the HTML output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_NS_MEMB_FILE_SCOPE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS = YES
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: YES.
+
+HAVE_DOT = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
+# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
+# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP = YES
diff --git a/doxygen/Makefile b/doxygen/Makefile
new file mode 100644
index 0000000..1cc4190
--- /dev/null
+++ b/doxygen/Makefile
@@ -0,0 +1,29 @@
+DESTDIR ?=
+PREFIX ?= /usr/local
+MANDIR ?= $(PREFIX)/share/man
+
+DOXYGEN := $(shell command -v doxygen 2> /dev/null)
+DOXYGENFLAGS =
+
+.PHONY: all
+all:
+ifndef DOXYGEN
+ $(warning "doxygen is not available please install it")
+else
+ $(DOXYGEN) $(DOXYGENFLAGS)
+endif
+
+.PHONY: clean
+clean:
+ifdef DOXYGEN
+ rm -Rf html
+ rm -Rf man
+endif
+
+.PHONY: install
+install:
+ifdef DOXYGEN
+ install -d $(DESTDIR)$(MANDIR)/man3
+ install man/man3/libtinyalsa-pcm.3 $(DESTDIR)$(MANDIR)/man3
+ install man/man3/libtinyalsa-mixer.3 $(DESTDIR)$(MANDIR)/man3
+endif
diff --git a/doxygen/mainpage.h b/doxygen/mainpage.h
new file mode 100644
index 0000000..cb7e7d3
--- /dev/null
+++ b/doxygen/mainpage.h
@@ -0,0 +1,10 @@
+/** @mainpage Preamble
+ *
+ * Welcome to the documentation for the TinyALSA project.
+ * <br><br>
+ * To start, you may either view the @ref libtinyalsa-pcm or @ref libtinyalsa-mixer.
+ * <br><br>
+ * If you find an error in the documentation or an area for improvement,
+ * open an issue or send a pull request to the <a href="https://github.com/tinyalsa/tinyalsa">github page</a>.
+ */
+
diff --git a/examples/Makefile b/examples/Makefile
new file mode 100644
index 0000000..650966d
--- /dev/null
+++ b/examples/Makefile
@@ -0,0 +1,23 @@
+CROSS_COMPILE ?=
+
+CC = $(CROSS_COMPILE)gcc
+CFLAGS = -Wall -Wextra -Werror -Wfatal-errors -I ../include
+
+VPATH = ../src
+
+EXAMPLES += pcm-readi
+EXAMPLES += pcm-writei
+
+.PHONY: all
+all: $(EXAMPLES)
+
+pcm-readi pcm-writei: LDLIBS+=-ldl
+
+pcm-readi: pcm-readi.c -ltinyalsa
+
+pcm-writei: pcm-writei.c -ltinyalsa
+
+.PHONY: clean
+clean:
+ rm -f $(EXAMPLES)
+
diff --git a/examples/meson.build b/examples/meson.build
new file mode 100644
index 0000000..59a48a1
--- /dev/null
+++ b/examples/meson.build
@@ -0,0 +1,8 @@
+examples = ['pcm-readi', 'pcm-writei']
+
+foreach e : examples
+ executable(e, '@0@.c'.format(e),
+ include_directories: tinyalsa_includes,
+ link_with: tinyalsa,
+ install: false)
+endforeach
diff --git a/examples/pcm-readi.c b/examples/pcm-readi.c
new file mode 100644
index 0000000..8722b41
--- /dev/null
+++ b/examples/pcm-readi.c
@@ -0,0 +1,83 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <tinyalsa/pcm.h>
+
+static size_t read_frames(void **frames)
+{
+ unsigned int card = 0;
+ unsigned int device = 0;
+ int flags = PCM_IN;
+
+ const struct pcm_config config = {
+ .channels = 2,
+ .rate = 48000,
+ .format = PCM_FORMAT_S32_LE,
+ .period_size = 1024,
+ .period_count = 2,
+ .start_threshold = 1024,
+ .silence_threshold = 1024 * 2,
+ .stop_threshold = 1024 * 2
+ };
+
+ struct pcm *pcm = pcm_open(card, device, flags, &config);
+ if (pcm == NULL) {
+ fprintf(stderr, "failed to allocate memory for PCM\n");
+ return 0;
+ } else if (!pcm_is_ready(pcm)){
+ pcm_close(pcm);
+ fprintf(stderr, "failed to open PCM\n");
+ return 0;
+ }
+
+ unsigned int frame_size = pcm_frames_to_bytes(pcm, 1);
+ unsigned int frames_per_sec = pcm_get_rate(pcm);
+
+ *frames = malloc(frame_size * frames_per_sec);
+ if (*frames == NULL) {
+ fprintf(stderr, "failed to allocate frames\n");
+ pcm_close(pcm);
+ return 0;
+ }
+
+ int read_count = pcm_readi(pcm, *frames, frames_per_sec);
+
+ size_t byte_count = pcm_frames_to_bytes(pcm, read_count);
+
+ pcm_close(pcm);
+
+ return byte_count;
+}
+
+static int write_file(const void *frames, size_t size)
+{
+ FILE *output_file = fopen("audio.raw", "wb");
+ if (output_file == NULL) {
+ perror("failed to open 'audio.raw' for writing");
+ return EXIT_FAILURE;
+ }
+ fwrite(frames, 1, size, output_file);
+ fclose(output_file);
+ return 0;
+}
+
+int main(void)
+{
+ void *frames = NULL;
+ size_t size = 0;
+
+ size = read_frames(&frames);
+ if (size == 0) {
+ return EXIT_FAILURE;
+ }
+
+ if (write_file(frames, size) < 0) {
+ free(frames);
+ return EXIT_FAILURE;
+ }
+
+ free(frames);
+
+ return EXIT_SUCCESS;
+}
+
diff --git a/examples/pcm-writei.c b/examples/pcm-writei.c
new file mode 100644
index 0000000..19eafac
--- /dev/null
+++ b/examples/pcm-writei.c
@@ -0,0 +1,103 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <tinyalsa/pcm.h>
+
+static long int file_size(FILE * file)
+{
+ if (fseek(file, 0, SEEK_END) < 0) {
+ return -1;
+ }
+ long int file_size = ftell(file);
+ if (fseek(file, 0, SEEK_SET) < 0) {
+ return -1;
+ }
+ return file_size;
+}
+
+static size_t read_file(void ** frames){
+
+ FILE * input_file = fopen("audio.raw", "rb");
+ if (input_file == NULL) {
+ perror("failed to open 'audio.raw' for writing");
+ return 0;
+ }
+
+ long int size = file_size(input_file);
+ if (size < 0) {
+ perror("failed to get file size of 'audio.raw'");
+ fclose(input_file);
+ return 0;
+ }
+
+ *frames = malloc(size);
+ if (*frames == NULL) {
+ fprintf(stderr, "failed to allocate frames\n");
+ fclose(input_file);
+ return 0;
+ }
+
+ size = fread(*frames, 1, size, input_file);
+
+ fclose(input_file);
+
+ return size;
+}
+
+static int write_frames(const void * frames, size_t byte_count){
+
+ unsigned int card = 0;
+ unsigned int device = 0;
+ int flags = PCM_OUT;
+
+ const struct pcm_config config = {
+ .channels = 2,
+ .rate = 48000,
+ .format = PCM_FORMAT_S32_LE,
+ .period_size = 1024,
+ .period_count = 2,
+ .start_threshold = 1024,
+ .silence_threshold = 1024 * 2,
+ .stop_threshold = 1024 * 2
+ };
+
+ struct pcm * pcm = pcm_open(card, device, flags, &config);
+ if (pcm == NULL) {
+ fprintf(stderr, "failed to allocate memory for PCM\n");
+ return -1;
+ } else if (!pcm_is_ready(pcm)){
+ pcm_close(pcm);
+ fprintf(stderr, "failed to open PCM\n");
+ return -1;
+ }
+
+ unsigned int frame_count = pcm_bytes_to_frames(pcm, byte_count);
+
+ int err = pcm_writei(pcm, frames, frame_count);
+ if (err < 0) {
+ printf("error: %s\n", pcm_get_error(pcm));
+ }
+
+ pcm_close(pcm);
+
+ return 0;
+}
+
+int main(void)
+{
+ void *frames;
+ size_t size;
+
+ size = read_file(&frames);
+ if (size == 0) {
+ return EXIT_FAILURE;
+ }
+
+ if (write_frames(frames, size) < 0) {
+ return EXIT_FAILURE;
+ }
+
+ free(frames);
+ return EXIT_SUCCESS;
+}
+
diff --git a/include/tinyalsa/asoundlib.h b/include/tinyalsa/asoundlib.h
new file mode 100644
index 0000000..bae0d03
--- /dev/null
+++ b/include/tinyalsa/asoundlib.h
@@ -0,0 +1,37 @@
+/* asoundlib.h
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+
+#ifndef TINYALSA_ASOUNDLIB_H
+#define TINYALSA_ASOUNDLIB_H
+
+#include "mixer.h"
+#include "pcm.h"
+#include "version.h"
+
+#endif
+
diff --git a/include/tinyalsa/attributes.h b/include/tinyalsa/attributes.h
new file mode 100644
index 0000000..f465ba1
--- /dev/null
+++ b/include/tinyalsa/attributes.h
@@ -0,0 +1,44 @@
+#ifndef TINYALSA_ATTRIBUTES_H
+#define TINYALSA_ATTRIBUTES_H
+
+/** @defgroup libtinyalsa-attributes
+ * @brief GCC attributes to issue diagnostics
+ * when the library is being used incorrectly.
+ * */
+
+// FIXME: Disable the deprecated attribute in Android temporarily. pcm_read/write are marked as
+// deprecated functions in the latest tinyalsa in GitHub. However, there are lots of libraries in
+// Android using these functions and building with -Werror flags. Besides build breakage, the
+// behavior and interface of the successors, pcm_readi/writei, are also changed. Once all have
+// been cleaned up, we will enable this again.
+#if defined(__GNUC__) && !defined(ANDROID)
+
+/** Issues a warning when a function is being
+ * used that is now deprecated.
+ * @ingroup libtinyalsa-attributes
+ * */
+#define TINYALSA_DEPRECATED __attribute__((deprecated))
+
+/** Issues a warning when a return code of
+ * a function is not checked.
+ * @ingroup libtinyalsa-attributes
+ * */
+#define TINYALSA_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+
+#else /* __GNUC__ && !ANDROID */
+
+/** This is just a placeholder for compilers
+ * that aren't GCC or Clang.
+ * @ingroup libtinyalsa-attributes
+ * */
+#define TINYALSA_DEPRECATED
+
+/** This is just a placeholder for compilers
+ * that aren't GCC or Clang.
+ * @ingroup libtinyalsa-attributes
+ * */
+#define TINYALSA_WARN_UNUSED_RESULT
+
+#endif /* __GNUC__ && !ANDROID */
+
+#endif /* TINYALSA_ATTRIBUTES_H */
diff --git a/include/tinyalsa/interval.h b/include/tinyalsa/interval.h
new file mode 100644
index 0000000..068571d
--- /dev/null
+++ b/include/tinyalsa/interval.h
@@ -0,0 +1,54 @@
+/* interval.h
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+
+#ifndef TINYALSA_INTERVAL_H
+#define TINYALSA_INTERVAL_H
+
+#include <stdlib.h>
+#include <unistd.h>
+
+/** A closed range signed interval. */
+
+struct tinyalsa_signed_interval {
+ /** The maximum value of the interval */
+ ssize_t max;
+ /** The minimum value of the interval */
+ ssize_t min;
+};
+
+/** A closed range unsigned interval. */
+
+struct tinyalsa_unsigned_interval {
+ /** The maximum value of the interval */
+ size_t max;
+ /** The minimum value of the interval */
+ size_t min;
+};
+
+#endif /* TINYALSA_INTERVAL_H */
+
diff --git a/include/tinyalsa/limits.h b/include/tinyalsa/limits.h
new file mode 100644
index 0000000..6f788c2
--- /dev/null
+++ b/include/tinyalsa/limits.h
@@ -0,0 +1,62 @@
+/* limits.h
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+
+#ifndef TINYALSA_LIMITS_H
+#define TINYALSA_LIMITS_H
+
+#include <tinyalsa/interval.h>
+
+#include <limits.h>
+#include <stdint.h>
+
+#define TINYALSA_SIGNED_INTERVAL_MAX SSIZE_MAX
+#define TINYALSA_SIGNED_INTERVAL_MIN SSIZE_MIN
+
+#define TINYALSA_UNSIGNED_INTERVAL_MAX SIZE_MAX
+#define TINYALSA_UNSIGNED_INTERVAL_MIN SIZE_MIN
+
+#define TINYALSA_CHANNELS_MAX 32U
+#define TINYALSA_CHANNELS_MIN 1U
+
+#define TINYALSA_FRAMES_MAX (ULONG_MAX / (TINYALSA_CHANNELS_MAX * 4))
+#define TINYALSA_FRAMES_MIN 0U
+
+#if TINYALSA_FRAMES_MAX > TINYALSA_UNSIGNED_INTERVAL_MAX
+#error "Frames max exceeds measurable value."
+#endif
+
+#if TINYALSA_FRAMES_MIN < TINYALSA_UNSIGNED_INTERVAL_MIN
+#error "Frames min exceeds measurable value."
+#endif
+
+extern const struct tinyalsa_unsigned_interval tinyalsa_channels_limit;
+
+extern const struct tinyalsa_unsigned_interval tinyalsa_frames_limit;
+
+#endif /* TINYALSA_LIMITS_H */
+
diff --git a/include/tinyalsa/meson.build b/include/tinyalsa/meson.build
new file mode 100644
index 0000000..d14b35b
--- /dev/null
+++ b/include/tinyalsa/meson.build
@@ -0,0 +1,10 @@
+tinyalsa_headers = [
+ 'asoundlib.h',
+ 'interval.h',
+ 'limits.h',
+ 'mixer.h',
+ 'pcm.h',
+ 'version.h'
+]
+
+install_headers(tinyalsa_headers, subdir: 'tinyalsa')
diff --git a/include/tinyalsa/mixer.h b/include/tinyalsa/mixer.h
new file mode 100644
index 0000000..7d0580f
--- /dev/null
+++ b/include/tinyalsa/mixer.h
@@ -0,0 +1,164 @@
+/* mixer.h
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+
+/** @file */
+
+/** @defgroup libtinyalsa-mixer Mixer Interface
+ * @brief All macros, structures and functions that make up the mixer interface.
+ */
+
+#ifndef TINYALSA_MIXER_H
+#define TINYALSA_MIXER_H
+
+#include <sys/time.h>
+#include <stddef.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct mixer;
+
+struct mixer_ctl;
+
+// mixer_ctl_event is a mirroring structure of snd_ctl_event
+struct mixer_ctl_event {
+ int type;
+ union {
+ struct {
+ unsigned int mask;
+ struct {
+ unsigned int numid;
+ int iface;
+ unsigned int device;
+ unsigned int subdevice;
+ unsigned char name[44];
+ unsigned int index;
+ } id;
+ } element;
+ unsigned char data[60];
+ } data;
+};
+
+/** Mixer control type.
+ * @ingroup libtinyalsa-mixer
+ */
+enum mixer_ctl_type {
+ /** boolean control type */
+ MIXER_CTL_TYPE_BOOL,
+ /** integer control type */
+ MIXER_CTL_TYPE_INT,
+ /** an enumerated control type */
+ MIXER_CTL_TYPE_ENUM,
+ MIXER_CTL_TYPE_BYTE,
+ MIXER_CTL_TYPE_IEC958,
+ /** a 64 bit integer control type */
+ MIXER_CTL_TYPE_INT64,
+ /** unknown control type */
+ MIXER_CTL_TYPE_UNKNOWN,
+ /** end of the enumeration (not a control type) */
+ MIXER_CTL_TYPE_MAX,
+};
+
+struct mixer *mixer_open(unsigned int card);
+
+void mixer_close(struct mixer *mixer);
+
+int mixer_add_new_ctls(struct mixer *mixer);
+
+const char *mixer_get_name(const struct mixer *mixer);
+
+unsigned int mixer_get_num_ctls(const struct mixer *mixer);
+
+unsigned int mixer_get_num_ctls_by_name(const struct mixer *mixer, const char *name);
+
+const struct mixer_ctl *mixer_get_ctl_const(const struct mixer *mixer, unsigned int id);
+
+struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id);
+
+struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name);
+
+struct mixer_ctl *mixer_get_ctl_by_name_and_index(struct mixer *mixer,
+ const char *name,
+ unsigned int index);
+
+int mixer_subscribe_events(struct mixer *mixer, int subscribe);
+
+int mixer_wait_event(struct mixer *mixer, int timeout);
+
+unsigned int mixer_ctl_get_id(const struct mixer_ctl *ctl);
+
+const char *mixer_ctl_get_name(const struct mixer_ctl *ctl);
+
+enum mixer_ctl_type mixer_ctl_get_type(const struct mixer_ctl *ctl);
+
+const char *mixer_ctl_get_type_string(const struct mixer_ctl *ctl);
+
+unsigned int mixer_ctl_get_num_values(const struct mixer_ctl *ctl);
+
+unsigned int mixer_ctl_get_num_enums(const struct mixer_ctl *ctl);
+
+const char *mixer_ctl_get_enum_string(struct mixer_ctl *ctl, unsigned int enum_id);
+
+/* Some sound cards update their controls due to external events,
+ * such as HDMI EDID byte data changing when an HDMI cable is
+ * connected. This API allows the count of elements to be updated.
+ */
+void mixer_ctl_update(struct mixer_ctl *ctl);
+
+int mixer_ctl_is_access_tlv_rw(const struct mixer_ctl *ctl);
+
+/* Set and get mixer controls */
+int mixer_ctl_get_percent(const struct mixer_ctl *ctl, unsigned int id);
+
+int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent);
+
+int mixer_ctl_get_value(const struct mixer_ctl *ctl, unsigned int id);
+
+int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count);
+
+int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value);
+
+int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count);
+
+int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string);
+
+/* Determine range of integer mixer controls */
+int mixer_ctl_get_range_min(const struct mixer_ctl *ctl);
+
+int mixer_ctl_get_range_max(const struct mixer_ctl *ctl);
+
+int mixer_read_event(struct mixer *mixer, struct mixer_ctl_event *event);
+
+int mixer_consume_event(struct mixer *mixer);
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif
+
+#endif
+
diff --git a/include/tinyalsa/pcm.h b/include/tinyalsa/pcm.h
new file mode 100644
index 0000000..6569146
--- /dev/null
+++ b/include/tinyalsa/pcm.h
@@ -0,0 +1,373 @@
+/* pcm.h
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+
+/** @file */
+
+/** @defgroup libtinyalsa-pcm PCM Interface
+ * @brief All macros, structures and functions that make up the PCM interface.
+ */
+
+#ifndef TINYALSA_PCM_H
+#define TINYALSA_PCM_H
+
+#include <tinyalsa/attributes.h>
+
+#include <sys/time.h>
+#include <stddef.h>
+
+/** A flag that specifies that the PCM is an output.
+ * May not be bitwise AND'd with @ref PCM_IN.
+ * Used in @ref pcm_open.
+ * @ingroup libtinyalsa-pcm
+ */
+#define PCM_OUT 0x00000000
+
+/** Specifies that the PCM is an input.
+ * May not be bitwise AND'd with @ref PCM_OUT.
+ * Used in @ref pcm_open.
+ * @ingroup libtinyalsa-pcm
+ */
+#define PCM_IN 0x10000000
+
+/** Specifies that the PCM will use mmap read and write methods.
+ * Used in @ref pcm_open.
+ * @ingroup libtinyalsa-pcm
+ */
+#define PCM_MMAP 0x00000001
+
+/** Specifies no interrupt requests.
+ * May only be bitwise AND'd with @ref PCM_MMAP.
+ * Used in @ref pcm_open.
+ * @ingroup libtinyalsa-pcm
+ */
+#define PCM_NOIRQ 0x00000002
+
+/** When set, calls to @ref pcm_write
+ * for a playback stream will not attempt
+ * to restart the stream in the case of an
+ * underflow, but will return -EPIPE instead.
+ * After the first -EPIPE error, the stream
+ * is considered to be stopped, and a second
+ * call to pcm_write will attempt to restart
+ * the stream.
+ * Used in @ref pcm_open.
+ * @ingroup libtinyalsa-pcm
+ */
+#define PCM_NORESTART 0x00000004
+
+/** Specifies monotonic timestamps.
+ * Used in @ref pcm_open.
+ * @ingroup libtinyalsa-pcm
+ */
+#define PCM_MONOTONIC 0x00000008
+
+/** If used with @ref pcm_open and @ref pcm_params_get,
+ * it will not cause the function to block if
+ * the PCM is not available. It will also cause
+ * the functions @ref pcm_readi and @ref pcm_writei
+ * to exit if they would cause the caller to wait.
+ * @ingroup libtinyalsa-pcm
+ * */
+#define PCM_NONBLOCK 0x00000010
+
+/** Means a PCM is opened
+ * @ingroup libtinyalsa-pcm
+ */
+#define PCM_STATE_OPEN 0x00
+
+/** Means a PCM HW_PARAMS is set
+ * @ingroup libtinyalsa-pcm
+ */
+#define PCM_STATE_SETUP 0x01
+
+/** Means a PCM is prepared
+ * @ingroup libtinyalsa-pcm
+ */
+#define PCM_STATE_PREPARED 0x02
+
+/** For inputs, this means the PCM is recording audio samples.
+ * For outputs, this means the PCM is playing audio samples.
+ * @ingroup libtinyalsa-pcm
+ */
+#define PCM_STATE_RUNNING 0x03
+
+/** For inputs, this means an overrun occured.
+ * For outputs, this means an underrun occured.
+ */
+#define PCM_STATE_XRUN 0x04
+
+/** For outputs, this means audio samples are played.
+ * A PCM is in a draining state when it is coming to a stop.
+ */
+#define PCM_STATE_DRAINING 0x05
+
+/** Means a PCM is suspended.
+ * @ingroup libtinyalsa-pcm
+ */
+#define PCM_STATE_SUSPENDED 0x07
+
+/** Means a PCM has been disconnected.
+ * @ingroup libtinyalsa-pcm
+ */
+#define PCM_STATE_DISCONNECTED 0x08
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/** Audio sample format of a PCM.
+ * The first letter specifiers whether the sample is signed or unsigned.
+ * The letter 'S' means signed. The letter 'U' means unsigned.
+ * The following number is the amount of bits that the sample occupies in memory.
+ * Following the underscore, specifiers whether the sample is big endian or little endian.
+ * The letters 'LE' mean little endian.
+ * The letters 'BE' mean big endian.
+ * This enumeration is used in the @ref pcm_config structure.
+ * @ingroup libtinyalsa-pcm
+ */
+enum pcm_format {
+
+/* Note: This section must stay in the same
+ * order for binary compatibility with older
+ * versions of TinyALSA. */
+
+ PCM_FORMAT_INVALID = -1,
+ /** Signed 16-bit, little endian */
+ PCM_FORMAT_S16_LE = 0,
+ /** Signed, 32-bit, little endian */
+ PCM_FORMAT_S32_LE,
+ /** Signed, 8-bit */
+ PCM_FORMAT_S8,
+ /** Signed, 24-bit (32-bit in memory), little endian */
+ PCM_FORMAT_S24_LE,
+ /** Signed, 24-bit, little endian */
+ PCM_FORMAT_S24_3LE,
+
+/* End of compatibility section. */
+
+ /** Signed, 16-bit, big endian */
+ PCM_FORMAT_S16_BE,
+ /** Signed, 24-bit (32-bit in memory), big endian */
+ PCM_FORMAT_S24_BE,
+ /** Signed, 24-bit, big endian */
+ PCM_FORMAT_S24_3BE,
+ /** Signed, 32-bit, big endian */
+ PCM_FORMAT_S32_BE,
+ /** Max of the enumeration list, not an actual format. */
+ PCM_FORMAT_MAX
+};
+
+/** A bit mask of 256 bits (32 bytes) that describes some hardware parameters of a PCM */
+struct pcm_mask {
+ /** bits of the bit mask */
+ unsigned int bits[32 / sizeof(unsigned int)];
+};
+
+/** Encapsulates the hardware and software parameters of a PCM.
+ * @ingroup libtinyalsa-pcm
+ */
+struct pcm_config {
+ /** The number of channels in a frame */
+ unsigned int channels;
+ /** The number of frames per second */
+ unsigned int rate;
+ /** The number of frames in a period */
+ unsigned int period_size;
+ /** The number of periods in a PCM */
+ unsigned int period_count;
+ /** The sample format of a PCM */
+ enum pcm_format format;
+ /* Values to use for the ALSA start, stop and silence thresholds, and
+ * silence size. Setting any one of these values to 0 will cause the
+ * default tinyalsa values to be used instead.
+ * Tinyalsa defaults are as follows.
+ *
+ * start_threshold : period_count * period_size
+ * stop_threshold : period_count * period_size
+ * silence_threshold : 0
+ * silence_size : 0
+ */
+ /** The minimum number of frames required to start the PCM */
+ unsigned int start_threshold;
+ /** The minimum number of frames required to stop the PCM */
+ unsigned int stop_threshold;
+ /** The minimum number of frames to silence the PCM */
+ unsigned int silence_threshold;
+ /** The number of frames to overwrite the playback buffer when the playback underrun is greater
+ * than the silence threshold */
+ unsigned int silence_size;
+
+ unsigned int avail_min;
+};
+
+/** Enumeration of a PCM's hardware parameters.
+ * Each of these parameters is either a mask or an interval.
+ * @ingroup libtinyalsa-pcm
+ */
+enum pcm_param
+{
+ /** A mask that represents the type of read or write method available (e.g. interleaved, mmap). */
+ PCM_PARAM_ACCESS,
+ /** A mask that represents the @ref pcm_format available (e.g. @ref PCM_FORMAT_S32_LE) */
+ PCM_PARAM_FORMAT,
+ /** A mask that represents the subformat available */
+ PCM_PARAM_SUBFORMAT,
+ /** An interval representing the range of sample bits available (e.g. 8 to 32) */
+ PCM_PARAM_SAMPLE_BITS,
+ /** An interval representing the range of frame bits available (e.g. 8 to 64) */
+ PCM_PARAM_FRAME_BITS,
+ /** An interval representing the range of channels available (e.g. 1 to 2) */
+ PCM_PARAM_CHANNELS,
+ /** An interval representing the range of rates available (e.g. 44100 to 192000) */
+ PCM_PARAM_RATE,
+ PCM_PARAM_PERIOD_TIME,
+ /** The number of frames in a period */
+ PCM_PARAM_PERIOD_SIZE,
+ /** The number of bytes in a period */
+ PCM_PARAM_PERIOD_BYTES,
+ /** The number of periods for a PCM */
+ PCM_PARAM_PERIODS,
+ PCM_PARAM_BUFFER_TIME,
+ PCM_PARAM_BUFFER_SIZE,
+ PCM_PARAM_BUFFER_BYTES,
+ PCM_PARAM_TICK_TIME,
+}; /* enum pcm_param */
+
+struct pcm_params;
+
+struct pcm_params *pcm_params_get(unsigned int card, unsigned int device,
+ unsigned int flags);
+
+void pcm_params_free(struct pcm_params *pcm_params);
+
+const struct pcm_mask *pcm_params_get_mask(const struct pcm_params *pcm_params, enum pcm_param param);
+
+unsigned int pcm_params_get_min(const struct pcm_params *pcm_params, enum pcm_param param);
+
+unsigned int pcm_params_get_max(const struct pcm_params *pcm_params, enum pcm_param param);
+
+/* Converts the pcm parameters to a human readable string.
+ * The string parameter is a caller allocated buffer of size bytes,
+ * which is then filled up to size - 1 and null terminated,
+ * if size is greater than zero.
+ * The return value is the number of bytes copied to string
+ * (not including null termination) if less than size; otherwise,
+ * the number of bytes required for the buffer.
+ */
+int pcm_params_to_string(struct pcm_params *params, char *string, unsigned int size);
+
+/* Returns 1 if the pcm_format is present (format bit set) in
+ * the pcm_params structure; 0 otherwise, or upon unrecognized format.
+ */
+int pcm_params_format_test(struct pcm_params *params, enum pcm_format format);
+
+struct pcm;
+
+struct pcm *pcm_open(unsigned int card,
+ unsigned int device,
+ unsigned int flags,
+ const struct pcm_config *config);
+
+struct pcm *pcm_open_by_name(const char *name,
+ unsigned int flags,
+ const struct pcm_config *config);
+
+int pcm_close(struct pcm *pcm);
+
+int pcm_is_ready(const struct pcm *pcm);
+
+unsigned int pcm_get_channels(const struct pcm *pcm);
+
+const struct pcm_config * pcm_get_config(const struct pcm *pcm);
+
+unsigned int pcm_get_rate(const struct pcm *pcm);
+
+enum pcm_format pcm_get_format(const struct pcm *pcm);
+
+int pcm_get_file_descriptor(const struct pcm *pcm);
+
+const char *pcm_get_error(const struct pcm *pcm);
+
+int pcm_set_config(struct pcm *pcm, const struct pcm_config *config);
+
+unsigned int pcm_format_to_bits(enum pcm_format format);
+
+unsigned int pcm_get_buffer_size(const struct pcm *pcm);
+
+unsigned int pcm_frames_to_bytes(const struct pcm *pcm, unsigned int frames);
+
+unsigned int pcm_bytes_to_frames(const struct pcm *pcm, unsigned int bytes);
+
+int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail, struct timespec *tstamp);
+
+unsigned int pcm_get_subdevice(const struct pcm *pcm);
+
+int pcm_writei(struct pcm *pcm, const void *data, unsigned int frame_count) TINYALSA_WARN_UNUSED_RESULT;
+
+int pcm_readi(struct pcm *pcm, void *data, unsigned int frame_count) TINYALSA_WARN_UNUSED_RESULT;
+
+int pcm_write(struct pcm *pcm, const void *data, unsigned int count) TINYALSA_DEPRECATED;
+
+int pcm_read(struct pcm *pcm, void *data, unsigned int count) TINYALSA_DEPRECATED;
+
+int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count);
+
+int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count);
+
+int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset, unsigned int *frames);
+
+int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames);
+
+int pcm_mmap_avail(struct pcm *pcm);
+
+int pcm_mmap_get_hw_ptr(struct pcm* pcm, unsigned int *hw_ptr, struct timespec *tstamp);
+
+int pcm_get_poll_fd(struct pcm *pcm);
+
+int pcm_link(struct pcm *pcm1, struct pcm *pcm2);
+
+int pcm_unlink(struct pcm *pcm);
+
+int pcm_prepare(struct pcm *pcm);
+
+int pcm_start(struct pcm *pcm);
+
+int pcm_stop(struct pcm *pcm);
+
+int pcm_wait(struct pcm *pcm, int timeout);
+
+long pcm_get_delay(struct pcm *pcm);
+
+int pcm_ioctl(struct pcm *pcm, int code, ...) TINYALSA_DEPRECATED;
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif
+
+#endif
+
diff --git a/include/tinyalsa/plugin.h b/include/tinyalsa/plugin.h
new file mode 100644
index 0000000..b2f97b9
--- /dev/null
+++ b/include/tinyalsa/plugin.h
@@ -0,0 +1,269 @@
+/* plugin.h
+** Copyright (c) 2019-2020, The Linux Foundation.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+#ifndef TINYALSA_PLUGIN_H
+#define TINYALSA_PLUGIN_H
+
+#include <poll.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include <sound/asound.h>
+
+/* static initializers */
+
+#define SND_VALUE_ENUM(etexts, eitems) \
+ {.texts = etexts, .items = eitems}
+
+#define SND_VALUE_BYTES(csize) \
+ {.size = csize }
+
+#define SND_VALUE_INTEGER(icount, imin, imax, istep) \
+ {.count = icount, .min = imin, .max = imax, .step = istep }
+
+#define SND_VALUE_TLV_BYTES(csize, cget, cput) \
+ {.size = csize, .get = cget, .put = cput }
+
+/* pointer based initializers */
+#define INIT_SND_CONTROL_INTEGER(c, cname, cget, cput, cint, pval, pdata) \
+ { \
+ c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \
+ c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; \
+ c->type = SNDRV_CTL_ELEM_TYPE_INTEGER; \
+ c->name = cname; c->value = &cint; c->get = cget; c->put = cput; \
+ c->private_value = pval; c->private_data = pdata; \
+ }
+
+#define INIT_SND_CONTROL_BYTES(c, cname, cget, cput, cint, pval, pdata) \
+ { \
+ c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \
+ c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; \
+ c->type = SNDRV_CTL_ELEM_TYPE_BYTES; \
+ c->name = cname; c->value = &cint; c->get = cget; c->put = cput; \
+ c->private_value = pval; c->private_data = pdata; \
+ }
+
+#define INIT_SND_CONTROL_ENUM(c, cname, cget, cput, cenum, pval, pdata) \
+ { \
+ c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \
+ c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; \
+ c->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; \
+ c->name = cname; c->value = cenum; c->get = cget; c->put = cput; \
+ c->private_value = pval; c->private_data = pdata; \
+ }
+
+#define INIT_SND_CONTROL_TLV_BYTES(c, cname, cbytes, priv_val, priv_data) \
+ { \
+ c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \
+ c->access = SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE; \
+ c->type = SNDRV_CTL_ELEM_TYPE_BYTES; \
+ c->name = cname; c->value = &cbytes; \
+ c->private_value = priv_val; c->private_data = priv_data; \
+ }
+
+struct mixer_plugin;
+struct pcm_plugin;
+struct snd_node;
+
+/** Operations that are required to be registered by the plugin.
+ * @ingroup libtinyalsa-pcm
+ */
+struct pcm_plugin_ops {
+ /** open the pcm plugin */
+ int (*open) (struct pcm_plugin **plugin, unsigned int card,
+ unsigned int device, unsigned int flags);
+ /** close the pcm plugin */
+ int (*close) (struct pcm_plugin *plugin);
+ /** Set the PCM hardware parameters to the plugin */
+ int (*hw_params) (struct pcm_plugin *plugin,
+ struct snd_pcm_hw_params *params);
+ /** Set the PCM software parameters to the plugin */
+ int (*sw_params) (struct pcm_plugin *plugin,
+ struct snd_pcm_sw_params *params);
+ /** Synchronize the pointer */
+ int (*sync_ptr) (struct pcm_plugin *plugin,
+ struct snd_pcm_sync_ptr *sync_ptr);
+ /** Write frames to plugin to be rendered to output */
+ int (*writei_frames) (struct pcm_plugin *plugin,
+ struct snd_xferi *x);
+ /** Read frames from plugin captured from input */
+ int (*readi_frames) (struct pcm_plugin *plugin,
+ struct snd_xferi *x);
+ /** Obtain the timestamp for the PCM */
+ int (*ttstamp) (struct pcm_plugin *plugin,
+ int *tstamp);
+ /** Prepare the plugin for data transfer */
+ int (*prepare) (struct pcm_plugin *plugin);
+ /** Start data transfer from/to the plugin */
+ int (*start) (struct pcm_plugin *plugin);
+ /** Drop pcm frames */
+ int (*drop) (struct pcm_plugin *plugin);
+ /** Any custom or alsa specific ioctl implementation */
+ int (*ioctl) (struct pcm_plugin *plugin,
+ int cmd, void *arg);
+ void *(*mmap) (struct pcm_plugin *plugin, void *addr, size_t length,
+ int prot, int flags, off_t offset);
+ int (*munmap) (struct pcm_plugin *plugin, void *addr, size_t length);
+ int (*poll) (struct pcm_plugin *plugin, struct pollfd *pfd, nfds_t nfds,
+ int timeout);
+};
+
+/** Minimum and maximum values for hardware parameter constraints.
+ * @ingroup libtinyalsa-pcm
+ */
+struct pcm_plugin_min_max {
+ /** Minimum value for the hardware parameter */
+ unsigned int min;
+ /** Maximum value for the hardware parameter */
+ unsigned int max;
+};
+
+/** Encapsulate the hardware parameter constraints
+ * @ingroup libtinyalsa-pcm
+ */
+struct pcm_plugin_hw_constraints {
+ /** Value for SNDRV_PCM_HW_PARAM_ACCESS param */
+ uint64_t access;
+ /** Value for SNDRV_PCM_HW_PARAM_FORMAT param.
+ * As of this implementation ALSA supports 52 formats */
+ uint64_t format;
+ /** Value for SNDRV_PCM_HW_PARAM_SAMPLE_BITS param */
+ struct pcm_plugin_min_max bit_width;
+ /** Value for SNDRV_PCM_HW_PARAM_CHANNELS param */
+ struct pcm_plugin_min_max channels;
+ /** Value for SNDRV_PCM_HW_PARAM_RATE param */
+ struct pcm_plugin_min_max rate;
+ /** Value for SNDRV_PCM_HW_PARAM_PERIODS param */
+ struct pcm_plugin_min_max periods;
+ /** Value for SNDRV_PCM_HW_PARAM_PERIOD_BYTES param */
+ struct pcm_plugin_min_max period_bytes;
+};
+
+struct pcm_plugin {
+ /** Card number for the pcm device */
+ unsigned int card;
+ /** device number for the pcm device */
+ unsigned int device;
+ /** pointer to the contraints registered by the plugin */
+ struct pcm_plugin_hw_constraints *constraints;
+ /** Indicates read/write mode, etc.. */
+ int mode;
+ /* Pointer to hold the plugin's private data */
+ void *priv;
+ /* Tracks the plugin state */
+ unsigned int state;
+};
+
+typedef void (*mixer_event_callback)(struct mixer_plugin *);
+
+struct mixer_plugin_ops {
+ int (*open) (struct mixer_plugin **plugin, unsigned int card);
+ void (*close) (struct mixer_plugin **plugin);
+ int (*subscribe_events) (struct mixer_plugin *plugin,
+ mixer_event_callback event_cb);
+ ssize_t (*read_event) (struct mixer_plugin *plugin,
+ struct snd_ctl_event *ev, size_t size);
+};
+
+struct snd_control {
+ snd_ctl_elem_iface_t iface;
+ unsigned int access;
+ const char *name;
+ snd_ctl_elem_type_t type;
+ void *value;
+ int (*get) (struct mixer_plugin *plugin,
+ struct snd_control *control,
+ struct snd_ctl_elem_value *ev);
+ int (*put) (struct mixer_plugin *plugin,
+ struct snd_control *control,
+ struct snd_ctl_elem_value *ev);
+ uint32_t private_value;
+ void *private_data;
+};
+
+struct mixer_plugin {
+ unsigned int card;
+ void *priv;
+
+ int eventfd;
+ int subscribed;
+ int event_cnt;
+
+ struct snd_control *controls;
+ unsigned int num_controls;
+};
+
+struct snd_value_enum {
+ unsigned int items;
+ char **texts;
+};
+
+struct snd_value_bytes {
+ unsigned int size;
+};
+
+struct snd_value_tlv_bytes {
+ unsigned int size;
+ int (*get) (struct mixer_plugin *plugin,
+ struct snd_control *control,
+ struct snd_ctl_tlv *tlv);
+ int (*put) (struct mixer_plugin *plugin,
+ struct snd_control *control,
+ struct snd_ctl_tlv *tlv);
+};
+
+struct snd_value_int {
+ unsigned int count;
+ int min;
+ int max;
+ int step;
+};
+
+/** Operations defined by the plugin.
+ * */
+struct snd_node_ops {
+ /** Function pointer to get card definition */
+ void* (*open_card)(unsigned int card);
+ /** Function pointer to release card definition */
+ void (*close_card)(void *card);
+ /** Get interger type properties from device definition */
+ int (*get_int)(void *node, const char *prop, int *val);
+ /** Get string type properties from device definition */
+ int (*get_str)(void *node, const char *prop, char **val);
+ /** Function pointer to get mixer definition */
+ void* (*get_mixer)(void *card);
+ /** Function pointer to get PCM definition */
+ void* (*get_pcm)(void *card, unsigned int id);
+ /** Reserved for other nodes such as compress */
+ void* reserved[4];
+};
+
+#endif /* end of TINYALSA_PLUGIN_H */
diff --git a/include/tinyalsa/version.h b/include/tinyalsa/version.h
new file mode 100644
index 0000000..f7fa5db
--- /dev/null
+++ b/include/tinyalsa/version.h
@@ -0,0 +1,58 @@
+/* version.h
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+
+#ifndef TINYALSA_VERSION_H
+#define TINYALSA_VERSION_H
+
+/* Macros for expanding the version numbers into string literals */
+#define TINYALSA_VERSION_STR_EX(number) #number
+#define TINYALSA_VERSION_STR(number) TINYALSA_VERSION_STR_EX (number)
+
+#define TINYALSA_VERSION_MAJOR 2
+
+#define TINYALSA_VERSION_MINOR 0
+
+#define TINYALSA_VERSION_PATCH 0
+
+/* The final version number is constructed based on minor, major and patch */
+#define TINYALSA_VERSION \
+ ((unsigned long) \
+ ((TINYALSA_VERSION_MAJOR << 16) | \
+ (TINYALSA_VERSION_MINOR << 8 ) | \
+ (TINYALSA_VERSION_PATCH )))
+
+/* The version string is constructed by concatenating individual ver. strings */
+#define TINYALSA_VERSION_STRING \
+ TINYALSA_VERSION_STR (TINYALSA_VERSION_MAJOR) \
+ "." \
+ TINYALSA_VERSION_STR (TINYALSA_VERSION_MINOR) \
+ "." \
+ TINYALSA_VERSION_STR (TINYALSA_VERSION_PATCH)
+
+#endif /* TINYALSA_VERSION_H */
+
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..213f8c8
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,38 @@
+project ('tinyalsa', 'c',
+ version : run_command(find_program('scripts/version.sh'), 'print', '-s').stdout().strip(),
+ meson_version : '>= 0.48.0')
+
+tinyalsa_includes = include_directories('.', 'include')
+
+cc = meson.get_compiler('c')
+
+# Dependency on libdl
+dl_dep = cc.find_library('dl')
+
+tinyalsa = library('tinyalsa',
+ 'src/mixer.c', 'src/pcm.c', 'src/pcm_hw.c', 'src/pcm_plugin.c', 'src/snd_card_plugin.c', 'src/mixer_hw.c', 'src/mixer_plugin.c',
+ include_directories: tinyalsa_includes,
+ version: meson.project_version(),
+ install: true,
+ dependencies: dl_dep)
+
+# For use as a Meson subproject
+tinyalsa_dep = declare_dependency(link_with: tinyalsa,
+ include_directories: include_directories('include'))
+
+if not get_option('docs').disabled()
+ # subdir('docs') # FIXME
+endif
+
+if not get_option('examples').disabled()
+ subdir('examples')
+endif
+
+subdir('include/tinyalsa')
+
+if not get_option('utils').disabled()
+ subdir('utils')
+endif
+
+pkg = import('pkgconfig')
+pkg.generate(tinyalsa, description: 'TinyALSA Library')
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644
index 0000000..f2e2bc2
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1,6 @@
+option('docs', type: 'feature', value: 'auto', yield: true,
+ description : 'Generate documentation with Doxygen')
+option('examples', type: 'feature', value: 'auto', yield: true,
+ description : 'Build examples')
+option('utils', type: 'feature', value: 'auto', yield: true,
+ description : 'Build utility tools')
diff --git a/scripts/travis-build.sh b/scripts/travis-build.sh
new file mode 100755
index 0000000..2bfc4d2
--- /dev/null
+++ b/scripts/travis-build.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+set -e
+set -u
+
+ROOT=$(pwd)
+
+make
+make clean
+
+mkdir cmake-build
+cd cmake-build
+cmake ..
+cmake --build .
+cd ..
+
+$HOME/.local/bin/meson . meson-build
+cd meson-build
+ninja
+cd ..
+
+${ROOT}/scripts/version.sh check
diff --git a/scripts/version.sh b/scripts/version.sh
new file mode 100755
index 0000000..9ed27a9
--- /dev/null
+++ b/scripts/version.sh
@@ -0,0 +1,260 @@
+#!/bin/sh
+
+# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+#
+# Project configuration variables
+#
+# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+VERSION_FILE="include/tinyalsa/version.h"
+CHANGELOG_FILE="debian/changelog"
+
+# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+#
+# Scripts internal variables
+#
+# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+LF="\n"
+PARAMS=""
+DRYRUN=0
+
+# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+#
+# Helper functions
+#
+# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+die()
+{
+ echo "Error: $@" 1>&2
+ exit 1
+}
+
+print_usage()
+{
+ echo
+ echo "Usage: $0 [OPTIONS] ACTION"
+ echo
+ echo "Available options:"
+ echo " -s,--script Format output in \"script\" mode (no trailing newline)."
+ echo " -d,--dry-run Does not commit anything to any file, just prints."
+ echo
+ echo "Available actions:"
+ echo " print [minor|major|patch] Print the current version."
+ echo " release [minor|major|patch] Bump the specified version part"
+ echo " check Check the changelog latest released"
+ echo " version against the version file."
+ echo
+ echo "Please run this script from the project root folder."
+ echo
+}
+
+check_files()
+{
+ [ -f ${VERSION_FILE} ] || die "No ${VERSION_FILE} found!";
+ [ -f ${CHANGELOG_FILE} ] || die "No ${CHANGELOG_FILE} found!"
+}
+
+# Gets a part of the version from the project version file (version.h).
+# Takes one argument: the matching version identifier in the version file, e.g.
+# TINYALSA_VERSION_MAJOR
+get_version_part()
+{
+ set -- "$1" "$(grep -m 1 "^#define\([ \t]*\)$1" ${VERSION_FILE} | sed 's/[^0-9]*//g')"
+
+ if [ -z "$2" ]; then
+ die "Could not get $1 from ${VERSION_FILE}"
+ fi
+
+ echo "$2"
+}
+
+
+# Gets the complete version from the version file.
+# Sets VERSION_MAJOR, VERSION_MINOR and VERSION_PATCH globals
+get_version()
+{
+ VERSION_MAJOR=$(get_version_part "TINYALSA_VERSION_MAJOR")
+ VERSION_MINOR=$(get_version_part "TINYALSA_VERSION_MINOR")
+ VERSION_PATCH=$(get_version_part "TINYALSA_VERSION_PATCH")
+}
+
+# Commits the new version part to the version file.
+# Takes two arguments: the version part identifier in the version file and the
+# new version number. If no arguments, do nothing.
+commit_version_part()
+{
+ if [ -z $1 ] || [ -z $2 ]; then
+ return 0
+ fi
+
+ sed -i "s/\(^#define[ \t]*$1\)[ \t]*\([0-9]*\)/\1 $2/g" ${VERSION_FILE} \
+ || die "Could not commit version for $1";
+
+ [ $(get_version_part $1) = "$2" ] || die "Version check after commit failed for $1"
+
+ return 0;
+}
+
+# Commits the new version to the version file.
+# Takes three arguments, the new version numbers for major, minor and patch
+commit_version()
+{
+ commit_version_part "TINYALSA_VERSION_PATCH" $1
+ commit_version_part "TINYALSA_VERSION_MINOR" $2
+ commit_version_part "TINYALSA_VERSION_MAJOR" $3
+
+ return 0
+}
+
+# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+#
+# Actions implementations / functions
+#
+# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+print_version()
+{
+ if [ -z $1 ]; then
+ printf "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}${LF}"
+ else
+ case "$1" in
+ major)
+ printf "${VERSION_MAJOR}${LF}"
+ ;;
+ minor)
+ printf "${VERSION_MINOR}${LF}"
+ ;;
+ patch)
+ printf "${VERSION_PATCH}${LF}"
+ ;;
+ *)
+ die "Unknown part \"$1\" (must be one of minor, major and patch)."
+ ;;
+ esac
+ fi
+
+ return 0
+}
+
+bump_version()
+{
+ case "${1:-patch}" in
+ major)
+ VERSION_MAJOR=$((VERSION_MAJOR+1))
+ VERSION_MINOR=0
+ VERSION_PATCH=0
+ ;;
+ minor)
+ VERSION_MINOR=$((VERSION_MINOR+1))
+ VERSION_PATCH=0
+ ;;
+ patch)
+ VERSION_PATCH=$((VERSION_PATCH+1))
+ ;;
+ *)
+ die "Unknown part \"$1\" (must be one of minor, major and patch)."
+ ;;
+ esac
+
+ if [ ${DRYRUN} -ne 1 ]; then
+ commit_version ${VERSION_PATCH} ${VERSION_MINOR} ${VERSION_MAJOR}
+ fi
+
+ print_version
+ return 0
+}
+
+check_version()
+{
+ # set $1 to log version, and $2 to ref version
+ set -- \
+ "$(grep -m 1 "^tinyalsa (" ${CHANGELOG_FILE}| sed "s/[^0-9.]*//g")" \
+ "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}"
+
+ if [ "$1" != "$2" ]; then
+ die "Changelog version ($1) does not match package version ($2)."
+ fi
+
+ printf "Changelog version ($1) OK!${LF}"
+ return 0
+}
+
+# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+#
+# Command Line parsing
+#
+# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+parse_command()
+{
+ if [ "$#" -eq "0" ]; then
+ print_usage
+ exit 1
+ fi
+
+ case "$1" in
+ print)
+ get_version
+ print_version "$2"
+ exit $?
+ ;;
+ release)
+ get_version
+ bump_version "$2"
+ exit $?
+ ;;
+ check)
+ get_version
+ check_version
+ exit $?
+ ;;
+ *)
+ die "Unsupported action \"$1\"."
+ ;;
+ esac
+}
+
+# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+#
+# Main
+#
+# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+set -e
+trap "set +e" 0
+
+# Checking parameters
+if [ "$#" -eq "0" ]; then
+ print_usage
+ exit 0
+fi
+
+while [ "$#" -ne "0" ]; do
+ case "$1" in
+ -s|--script)
+ unset LF
+ shift
+ ;;
+ -d|--dry-run)
+ DRYRUN=1
+ shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*|--*=)
+ die "Unsupported flag \"$1\"."
+ ;;
+ *)
+ PARAMS="$PARAMS ${1}"
+ shift
+ ;;
+ esac
+done
+
+# set positional arguments in their proper place
+set -- "${PARAMS}"
+
+check_files
+parse_command ${PARAMS}
+
+# The script should never reach this place.
+die "Internal error. Please report this."
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..aaa84b8
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,69 @@
+DESTDIR ?=
+PREFIX ?= /usr/local
+LIBDIR ?= $(PREFIX)/lib
+BINDIR ?= $(PREFIX)/bin
+ifdef DEB_HOST_MULTIARCH
+LIBDIR := $(LIBDIR)/$(DEB_HOST_MULTIARCH)
+endif
+
+CC = $(CROSS_COMPILE)gcc
+AR = $(CROSS_COMPILE)ar
+LD = $(CROSS_COMPILE)gcc
+
+WARNINGS = -Wall -Wextra -Werror -Wfatal-errors
+INCLUDE_DIRS = -I ../include
+override CFLAGS := $(WARNINGS) $(INCLUDE_DIRS) -fPIC $(CFLAGS)
+
+VPATH = ../include/tinyalsa
+OBJECTS = limits.o mixer.o pcm.o pcm_plugin.o pcm_hw.o snd_card_plugin.o mixer_plugin.o mixer_hw.o
+
+LIBVERSION_MAJOR = $(TINYALSA_VERSION_MAJOR)
+LIBVERSION = $(TINYALSA_VERSION)
+
+.PHONY: all
+all: libtinyalsa.a libtinyalsa.so
+
+pcm.o: pcm.c limits.h pcm.h pcm_io.h plugin.h snd_card_plugin.h
+
+pcm_plugin.o: pcm_plugin.c asoundlib.h pcm_io.h plugin.h snd_card_plugin.h
+
+pcm_hw.o: pcm_hw.c asoundlib.h pcm_io.h
+
+limits.o: limits.c limits.h
+
+mixer.o: mixer.c mixer.h mixer_io.h plugin.h
+
+snd_card_plugin.o: snd_card_plugin.c plugin.h snd_card_plugin.h
+
+mixer_plugin.o: mixer_plugin.c mixer_io.h plugin.h snd_card_plugin.h
+
+mixer_hw.o: mixer_hw.c mixer_io.h
+
+libtinyalsa.a: $(OBJECTS)
+ $(AR) $(ARFLAGS) $@ $^
+
+libtinyalsa.so: libtinyalsa.so.$(LIBVERSION_MAJOR)
+ ln -sf $< $@
+
+libtinyalsa.so.$(LIBVERSION_MAJOR): libtinyalsa.so.$(LIBVERSION)
+ ln -sf $< $@
+
+libtinyalsa.so.$(LIBVERSION): $(OBJECTS)
+ $(LD) $(LDFLAGS) -shared -Wl,-soname,libtinyalsa.so.$(LIBVERSION_MAJOR) $^ -o $@
+
+.PHONY: clean
+clean:
+ rm -f libtinyalsa.a
+ rm -f libtinyalsa.so
+ rm -f libtinyalsa.so.$(LIBVERSION_MAJOR)
+ rm -f libtinyalsa.so.$(LIBVERSION)
+ rm -f $(OBJECTS)
+
+.PHONY: install
+install: libtinyalsa.a libtinyalsa.so.$(LIBVERSION_MAJOR)
+ install -d $(DESTDIR)$(LIBDIR)/
+ install libtinyalsa.a $(DESTDIR)$(LIBDIR)/
+ install libtinyalsa.so.$(LIBVERSION) $(DESTDIR)$(LIBDIR)/
+ ln -sf libtinyalsa.so.$(LIBVERSION) $(DESTDIR)$(LIBDIR)/libtinyalsa.so.$(LIBVERSION_MAJOR)
+ ln -sf libtinyalsa.so.$(LIBVERSION_MAJOR) $(DESTDIR)$(LIBDIR)/libtinyalsa.so
+
diff --git a/src/limits.c b/src/limits.c
new file mode 100644
index 0000000..25bd352
--- /dev/null
+++ b/src/limits.c
@@ -0,0 +1,12 @@
+#include <tinyalsa/limits.h>
+
+const struct tinyalsa_unsigned_interval tinyalsa_channels_limit = {
+ .max = TINYALSA_CHANNELS_MAX,
+ .min = TINYALSA_CHANNELS_MIN
+};
+
+const struct tinyalsa_unsigned_interval tinyalsa_frames_limit = {
+ .max = TINYALSA_FRAMES_MAX,
+ .min = TINYALSA_FRAMES_MIN
+};
+
diff --git a/src/mixer.c b/src/mixer.c
new file mode 100644
index 0000000..a45502e
--- /dev/null
+++ b/src/mixer.c
@@ -0,0 +1,1294 @@
+/* mixer.c
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <limits.h>
+#include <time.h>
+#include <poll.h>
+
+#include <sys/ioctl.h>
+
+#include <linux/ioctl.h>
+
+#ifndef __force
+#define __force
+#endif
+
+#ifndef __bitwise
+#define __bitwise
+#endif
+
+#ifndef __user
+#define __user
+#endif
+
+#include <sound/asound.h>
+
+#include <tinyalsa/mixer.h>
+#include <tinyalsa/plugin.h>
+
+#include "mixer_io.h"
+
+/** A mixer control.
+ * @ingroup libtinyalsa-mixer
+ */
+struct mixer_ctl {
+ /** The mixer that the mixer control belongs to */
+ struct mixer *mixer;
+ /** Information on the control's value (i.e. type, number of values) */
+ struct snd_ctl_elem_info info;
+ /** A list of string representations of enumerated values (only valid for enumerated controls) */
+ char **ename;
+ /** Pointer to the group that the control belongs to */
+ struct mixer_ctl_group *grp;
+};
+
+struct mixer_ctl_group {
+ /** A continuous array of mixer controls */
+ struct mixer_ctl *ctl;
+ /** The number of mixer controls */
+ unsigned int count;
+ /** The number of events associated with this group */
+ unsigned int event_cnt;
+ /** The operations corresponding to this group */
+ const struct mixer_ops *ops;
+ /** Private data for storing group specific data */
+ void *data;
+};
+
+/** A mixer handle.
+ * @ingroup libtinyalsa-mixer
+ */
+struct mixer {
+ /** File descriptor for the card */
+ int fd;
+ /** Card information */
+ struct snd_ctl_card_info card_info;
+ /* Hardware (kernel interface) mixer control group */
+ struct mixer_ctl_group *h_grp;
+ /* Virtual (Plugin interface) mixer control group */
+ struct mixer_ctl_group *v_grp;
+ /* Total count of mixer controls from both groups */
+ unsigned int total_count;
+ /* Flag to track if card information is already retrieved */
+ bool is_card_info_retrieved;
+
+};
+
+static void mixer_cleanup_control(struct mixer_ctl *ctl)
+{
+ unsigned int m;
+
+ if (ctl->ename) {
+ unsigned int max = ctl->info.value.enumerated.items;
+ for (m = 0; m < max; m++)
+ free(ctl->ename[m]);
+ free(ctl->ename);
+ }
+}
+
+static void mixer_grp_close(struct mixer *mixer, struct mixer_ctl_group *grp)
+{
+ unsigned int n;
+
+ if (!grp)
+ return;
+
+ if (grp->ctl) {
+ for (n = 0; n < grp->count; n++)
+ mixer_cleanup_control(&grp->ctl[n]);
+ free(grp->ctl);
+ }
+
+ free(grp);
+
+ mixer->is_card_info_retrieved = false;
+}
+
+/** Closes a mixer returned by @ref mixer_open.
+ * @param mixer A mixer handle.
+ * @ingroup libtinyalsa-mixer
+ */
+void mixer_close(struct mixer *mixer)
+{
+ if (!mixer)
+ return;
+
+ if (mixer->fd >= 0 && mixer->h_grp)
+ mixer->h_grp->ops->close(mixer->h_grp->data);
+ mixer_grp_close(mixer, mixer->h_grp);
+
+#ifdef TINYALSA_USES_PLUGINS
+ if (mixer->v_grp)
+ mixer->v_grp->ops->close(mixer->v_grp->data);
+ mixer_grp_close(mixer, mixer->v_grp);
+#endif
+
+ free(mixer);
+
+ /* TODO: verify frees */
+}
+
+static void *mixer_realloc_z(void *ptr, size_t curnum, size_t newnum, size_t size)
+{
+ int8_t *newp;
+
+ newp = realloc(ptr, size * newnum);
+ if (!newp)
+ return NULL;
+
+ memset(newp + (curnum * size), 0, (newnum - curnum) * size);
+ return newp;
+}
+
+static int add_controls(struct mixer *mixer, struct mixer_ctl_group *grp)
+{
+ struct snd_ctl_elem_list elist;
+ struct snd_ctl_elem_id *eid = NULL;
+ struct mixer_ctl *ctl;
+ const unsigned int old_count = grp->count;
+ unsigned int new_count;
+ unsigned int n;
+
+ memset(&elist, 0, sizeof(elist));
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
+ goto fail;
+
+ if (old_count == elist.count)
+ return 0; /* no new controls return unchanged */
+
+ if (old_count > elist.count)
+ return -1; /* driver has removed controls - this is bad */
+
+ ctl = mixer_realloc_z(grp->ctl, old_count, elist.count,
+ sizeof(struct mixer_ctl));
+ if (!ctl)
+ goto fail;
+
+ grp->ctl = ctl;
+
+ /* ALSA drivers are not supposed to remove or re-order controls that
+ * have already been created so we know that any new controls must
+ * be after the ones we have already collected
+ */
+ new_count = elist.count;
+ elist.space = new_count - old_count; /* controls we haven't seen before */
+ elist.offset = old_count; /* first control we haven't seen */
+
+ eid = calloc(elist.space, sizeof(struct snd_ctl_elem_id));
+ if (!eid)
+ goto fail;
+
+ elist.pids = eid;
+
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
+ goto fail;
+
+ for (n = old_count; n < new_count; n++) {
+ struct snd_ctl_elem_info *ei = &grp->ctl[n].info;
+ ei->id.numid = eid[n - old_count].numid;
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0)
+ goto fail_extend;
+ ctl[n].mixer = mixer;
+ ctl[n].grp = grp;
+ }
+
+ grp->count = new_count;
+ mixer->total_count += (new_count - old_count);
+
+ free(eid);
+ return 0;
+
+fail_extend:
+ /* cleanup the control we failed on but leave the ones that were already
+ * added. Also no advantage to shrinking the resized memory block, we
+ * might want to extend the controls again later
+ */
+ mixer_cleanup_control(&ctl[n]);
+
+ grp->count = n; /* keep controls we successfully added */
+ mixer->total_count += (n - old_count);
+ /* fall through... */
+fail:
+ free(eid);
+ return -1;
+}
+
+static int mixer_grp_open(struct mixer *mixer, unsigned int card, bool is_hw)
+{
+ struct mixer_ctl_group *grp = NULL;
+ const struct mixer_ops *ops = NULL;
+ void *data = NULL;
+ int fd, ret;
+
+ grp = calloc(1, sizeof(*grp));
+ if (!grp)
+ return -ENOMEM;
+
+ if (is_hw) {
+ mixer->fd = -1;
+ fd = mixer_hw_open(card, &data, &ops);
+ if (fd < 0) {
+ ret = fd;
+ goto err_open;
+ }
+ mixer->fd = fd;
+ mixer->h_grp = grp;
+ }
+#ifdef TINYALSA_USES_PLUGINS
+ else {
+ ret = mixer_plugin_open(card, &data, &ops);
+ if (ret < 0)
+ goto err_open;
+ mixer->v_grp = grp;
+ }
+#endif
+ grp->ops = ops;
+ grp->data = data;
+
+ if (!mixer->is_card_info_retrieved) {
+ ret = grp->ops->ioctl(data, SNDRV_CTL_IOCTL_CARD_INFO,
+ &mixer->card_info);
+ if (ret < 0)
+ goto err_card_info;
+ mixer->is_card_info_retrieved = true;
+ }
+
+ ret = add_controls(mixer, grp);
+ if (ret < 0)
+ goto err_card_info;
+
+ return 0;
+
+err_card_info:
+ grp->ops->close(grp->data);
+
+err_open:
+ free(grp);
+ return ret;
+
+}
+
+/** Opens a mixer for a given card.
+ * @param card The card to open the mixer for.
+ * @returns An initialized mixer handle.
+ * @ingroup libtinyalsa-mixer
+ */
+struct mixer *mixer_open(unsigned int card)
+{
+ struct mixer *mixer = NULL;
+ int h_status, v_status = -1;
+
+ mixer = calloc(1, sizeof(*mixer));
+ if (!mixer)
+ goto fail;
+
+ h_status = mixer_grp_open(mixer, card, true);
+
+#ifdef TINYALSA_USES_PLUGINS
+ v_status = mixer_grp_open(mixer, card, false);
+#endif
+
+ /* both hw and virtual should fail for mixer_open to fail */
+ if (h_status < 0 && v_status < 0)
+ goto fail;
+
+ return mixer;
+
+fail:
+ if (mixer)
+ mixer_close(mixer);
+
+ return NULL;
+}
+
+/** Some controls may not be present at boot time, e.g. controls from runtime
+ * loadable DSP firmware. This function adds any new controls that have appeared
+ * since mixer_open() or the last call to this function. This assumes a well-
+ * behaved codec driver that does not delete controls that already exists, so
+ * any added controls must be after the last one we already saw. Scanning only
+ * the new controls is much faster than calling mixer_close() then mixer_open()
+ * to re-scan all controls.
+ *
+ * NOTE: this invalidates any struct mixer_ctl pointers previously obtained
+ * from mixer_get_ctl() and mixer_get_ctl_by_name(). Either refresh all your
+ * stored pointers after calling mixer_update_ctls(), or (better) do not
+ * store struct mixer_ctl pointers, instead lookup the control by name or
+ * id only when you are about to use it. The overhead of lookup by id
+ * using mixer_get_ctl() is negligible.
+ * @param mixer An initialized mixer handle.
+ * @returns 0 on success, -1 on failure
+ */
+int mixer_add_new_ctls(struct mixer *mixer)
+{
+ int rc1 = 0, rc2 = 0;
+
+ if (!mixer)
+ return 0;
+
+ /* add the h_grp controls */
+ if (mixer->h_grp)
+ rc1 = add_controls(mixer, mixer->h_grp);
+
+#ifdef TINYALSA_USES_PLUGINS
+ /* add the v_grp controls */
+ if (mixer->v_grp)
+ rc2 = add_controls(mixer, mixer->v_grp);
+#endif
+
+ if (rc1 < 0)
+ return rc1;
+ if (rc2 < 0)
+ return rc2;
+
+ return 0;
+}
+
+/** Gets the name of the mixer's card.
+ * @param mixer An initialized mixer handle.
+ * @returns The name of the mixer's card.
+ * @ingroup libtinyalsa-mixer
+ */
+const char *mixer_get_name(const struct mixer *mixer)
+{
+ return (const char *)mixer->card_info.name;
+}
+
+/** Gets the number of mixer controls for a given mixer.
+ * @param mixer An initialized mixer handle.
+ * @returns The number of mixer controls for the given mixer.
+ * @ingroup libtinyalsa-mixer
+ */
+unsigned int mixer_get_num_ctls(const struct mixer *mixer)
+{
+ if (!mixer)
+ return 0;
+
+ return mixer->total_count;
+}
+
+/** Gets the number of mixer controls, that go by a specified name, for a given mixer.
+ * @param mixer An initialized mixer handle.
+ * @param name The name of the mixer control
+ * @returns The number of mixer controls, specified by @p name, for the given mixer.
+ * @ingroup libtinyalsa-mixer
+ */
+unsigned int mixer_get_num_ctls_by_name(const struct mixer *mixer, const char *name)
+{
+ struct mixer_ctl_group *grp;
+ unsigned int n;
+ unsigned int count = 0;
+ struct mixer_ctl *ctl;
+
+ if (!mixer)
+ return 0;
+
+ if (mixer->h_grp) {
+ grp = mixer->h_grp;
+ ctl = grp->ctl;
+
+ for (n = 0; n < grp->count; n++)
+ if (!strcmp(name, (char*) ctl[n].info.id.name))
+ count++;
+ }
+#ifdef TINYALSA_USES_PLUGINS
+ if (mixer->v_grp) {
+ grp = mixer->v_grp;
+ ctl = grp->ctl;
+
+ for (n = 0; n < grp->count; n++)
+ if (!strcmp(name, (char*) ctl[n].info.id.name))
+ count++;
+ }
+#endif
+
+ return count;
+}
+
+/** Subscribes for the mixer events.
+ * @param mixer A mixer handle.
+ * @param subscribe value indicating subscribe or unsubscribe for events
+ * @returns On success, zero.
+ * On failure, non-zero.
+ * @ingroup libtinyalsa-mixer
+ */
+int mixer_subscribe_events(struct mixer *mixer, int subscribe)
+{
+ struct mixer_ctl_group *grp;
+
+ if (mixer->h_grp) {
+ grp = mixer->h_grp;
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0)
+ return -1;
+ }
+
+#ifdef TINYALSA_USES_PLUGINS
+ if (mixer->v_grp) {
+ grp = mixer->v_grp;
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0)
+ return -1;
+ }
+#endif
+ return 0;
+}
+
+/** Wait for mixer events.
+ * @param mixer A mixer handle.
+ * @param timeout timout value
+ * @returns On success, 1.
+ * On failure, -errno.
+ * On timeout, 0
+ * @ingroup libtinyalsa-mixer
+ */
+int mixer_wait_event(struct mixer *mixer, int timeout)
+{
+ struct pollfd *pfd;
+ struct mixer_ctl_group *grp;
+ int count = 0, num_fds = 0, i, ret = 0;
+
+ if (mixer->fd >= 0)
+ num_fds++;
+
+#ifdef TINYALSA_USES_PLUGINS
+ if (mixer->v_grp)
+ num_fds++;
+#endif
+
+ pfd = (struct pollfd *)calloc(num_fds, sizeof(struct pollfd));
+ if (!pfd)
+ return -ENOMEM;
+
+ if (mixer->fd >= 0) {
+ pfd[count].fd = mixer->fd;
+ pfd[count].events = POLLIN | POLLOUT | POLLERR | POLLNVAL;
+ count++;
+ }
+
+#ifdef TINYALSA_USES_PLUGINS
+ if (mixer->v_grp) {
+ grp = mixer->v_grp;
+ if (!grp->ops->get_poll_fd(grp->data, pfd, count)) {
+ pfd[count].events = POLLIN | POLLERR | POLLNVAL;
+ count++;
+ }
+ }
+#endif
+
+ if (!count)
+ goto exit;
+
+ for (;;) {
+ int err;
+ err = poll(pfd, count, timeout);
+ if (err < 0) {
+ ret = -errno;
+ goto exit;
+ }
+ if (!err)
+ goto exit;
+
+ for (i = 0; i < count; i++) {
+ if (pfd[i].revents & (POLLERR | POLLNVAL)) {
+ ret = -EIO;
+ goto exit;
+ }
+ if (pfd[i].revents & (POLLIN | POLLOUT)) {
+ if ((i == 0) && mixer->fd >= 0) {
+ grp = mixer->h_grp;
+ grp->event_cnt++;
+ }
+#ifdef TINYALSA_USES_PLUGINS
+ else {
+ grp = mixer->v_grp;
+ grp->event_cnt++;
+ }
+#endif
+ ret = 1;
+ goto exit;
+ }
+ }
+ }
+exit:
+ free(pfd);
+ return ret;
+}
+
+/** Consume a mixer event.
+ * If mixer_subscribe_events has been called,
+ * mixer_wait_event will identify when a control value has changed.
+ * This function will clear a single event from the mixer so that
+ * further events can be alerted.
+ *
+ * @param mixer A mixer handle.
+ * @returns 1 on success. 0, if no pending event. -errno on failure.
+ * @ingroup libtinyalsa-mixer
+ */
+int mixer_consume_event(struct mixer *mixer)
+{
+ struct mixer_ctl_event ev;
+
+ return mixer_read_event(mixer, &ev);
+}
+
+/** Read a mixer control event.
+ * Try to read an control event from mixer.
+ *
+ * @param mixer A mixer handle.
+ * @param event Output parameter. If there is an event read form the mixer, this function will fill
+ * the event data into it.
+ * @returns 1 on success. 0, if no pending event. -errno on failure.
+ * @ingroup libtinyalsa-mixer
+ */
+int mixer_read_event(struct mixer *mixer, struct mixer_ctl_event *event)
+{
+ struct mixer_ctl_group *grp = NULL;
+ struct snd_ctl_event ev;
+ ssize_t bytes = 0;
+
+ if (!mixer || !event) {
+ return -EINVAL;
+ }
+
+ if (mixer->h_grp) {
+ if (mixer->h_grp->event_cnt > 0) {
+ grp = mixer->h_grp;
+ }
+ }
+#ifdef TINYALSA_USES_PLUGINS
+ if (mixer->v_grp) {
+ if (mixer->v_grp->event_cnt > 0) {
+ grp = mixer->v_grp;
+ }
+ }
+#endif
+ if (grp) {
+ grp->event_cnt--;
+ bytes = grp->ops->read_event(grp->data, &ev, sizeof(ev));
+
+ if (bytes < 0) {
+ return -errno;
+ }
+
+ if (bytes == sizeof(*event)) {
+ memcpy(event, &ev, sizeof(*event));
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static unsigned int mixer_grp_get_count(struct mixer_ctl_group *grp)
+{
+ if (!grp)
+ return 0;
+
+ return grp->count;
+}
+
+/** Gets a mixer control handle, by the mixer control's id.
+ * For non-const access, see @ref mixer_get_ctl
+ * @param mixer An initialized mixer handle.
+ * @param id The control's id in the given mixer.
+ * @returns A handle to the mixer control.
+ * @ingroup libtinyalsa-mixer
+ */
+const struct mixer_ctl *mixer_get_ctl_const(const struct mixer *mixer, unsigned int id)
+{
+ unsigned int h_count;
+
+ if (!mixer || (id >= mixer->total_count))
+ return NULL;
+
+ h_count = mixer_grp_get_count(mixer->h_grp);
+
+ if (id < h_count)
+ return mixer->h_grp->ctl + id;
+#ifdef TINYALSA_USES_PLUGINS
+ else {
+ unsigned int v_count = mixer_grp_get_count(mixer->v_grp);
+ if ((id - h_count) < v_count)
+ return mixer->v_grp->ctl + (id - h_count);
+ }
+#endif
+
+ return NULL;
+}
+
+/** Gets a mixer control handle, by the mixer control's id.
+ * For const access, see @ref mixer_get_ctl_const
+ * @param mixer An initialized mixer handle.
+ * @param id The control's id in the given mixer.
+ * @returns A handle to the mixer control.
+ * @ingroup libtinyalsa-mixer
+ */
+struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id)
+{
+ unsigned int h_count;
+
+ if (!mixer || (id >= mixer->total_count))
+ return NULL;
+
+ h_count = mixer_grp_get_count(mixer->h_grp);
+
+ if (id < h_count)
+ return mixer->h_grp->ctl + id;
+#ifdef TINYALSA_USES_PLUGINS
+ else {
+ unsigned int v_count = mixer_grp_get_count(mixer->v_grp);
+ if ((id - h_count) < v_count)
+ return mixer->v_grp->ctl + (id - h_count);
+ }
+#endif
+ return NULL;
+}
+
+/** Gets the first instance of mixer control handle, by the mixer control's name.
+ * @param mixer An initialized mixer handle.
+ * @param name The control's name in the given mixer.
+ * @returns A handle to the mixer control.
+ * @ingroup libtinyalsa-mixer
+ */
+struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name)
+{
+ return mixer_get_ctl_by_name_and_index(mixer, name, 0);
+}
+
+/** Gets an instance of mixer control handle, by the mixer control's name and index.
+ * For instance, if two controls have the name of 'Volume', then and index of 1 would return the second control.
+ * @param mixer An initialized mixer handle.
+ * @param name The control's name in the given mixer.
+ * @param index The control's index.
+ * @returns A handle to the mixer control.
+ * @ingroup libtinyalsa-mixer
+ */
+struct mixer_ctl *mixer_get_ctl_by_name_and_index(struct mixer *mixer,
+ const char *name,
+ unsigned int index)
+{
+ struct mixer_ctl_group *grp;
+ unsigned int n;
+ struct mixer_ctl *ctl;
+
+ if (!mixer)
+ return NULL;
+
+ if (mixer->h_grp) {
+ grp = mixer->h_grp;
+ ctl = grp->ctl;
+
+ for (n = 0; n < grp->count; n++)
+ if (!strcmp(name, (char*) ctl[n].info.id.name))
+ if (index-- == 0)
+ return ctl + n;
+ }
+
+#ifdef TINYALSA_USES_PLUGINS
+ if (mixer->v_grp) {
+ grp = mixer->v_grp;
+ ctl = grp->ctl;
+
+ for (n = 0; n < grp->count; n++)
+ if (!strcmp(name, (char*) ctl[n].info.id.name))
+ if (index-- == 0)
+ return ctl + n;
+ }
+#endif
+ return NULL;
+}
+
+/** Updates the control's info.
+ * This is useful for a program that may be idle for a period of time.
+ * @param ctl An initialized control handle.
+ * @ingroup libtinyalsa-mixer
+ */
+void mixer_ctl_update(struct mixer_ctl *ctl)
+{
+ struct mixer_ctl_group *grp;
+
+ if (!ctl)
+ return;
+
+ grp = ctl->grp;
+ grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, &ctl->info);
+}
+
+/** Checks the control for TLV Read/Write access.
+ * @param ctl An initialized control handle.
+ * @returns On success, non-zero.
+ * On failure, zero.
+ * @ingroup libtinyalsa-mixer
+ */
+int mixer_ctl_is_access_tlv_rw(const struct mixer_ctl *ctl)
+{
+ return (ctl->info.access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE);
+}
+
+/** Gets the control's ID.
+ * @param ctl An initialized control handle.
+ * @returns On success, the control's ID is returned.
+ * On error, UINT_MAX is returned instead.
+ * @ingroup libtinyalsa-mixer
+ */
+unsigned int mixer_ctl_get_id(const struct mixer_ctl *ctl)
+{
+ if (!ctl)
+ return UINT_MAX;
+
+ /* numid values start at 1, return a 0-base value that
+ * can be passed to mixer_get_ctl()
+ */
+ return ctl->info.id.numid - 1;
+}
+
+/** Gets the name of the control.
+ * @param ctl An initialized control handle.
+ * @returns On success, the name of the control.
+ * On error, NULL.
+ * @ingroup libtinyalsa-mixer
+ */
+const char *mixer_ctl_get_name(const struct mixer_ctl *ctl)
+{
+ if (!ctl)
+ return NULL;
+
+ return (const char *)ctl->info.id.name;
+}
+
+/** Gets the value type of the control.
+ * @param ctl An initialized control handle
+ * @returns On success, the type of mixer control.
+ * On failure, it returns @ref MIXER_CTL_TYPE_UNKNOWN
+ * @ingroup libtinyalsa-mixer
+ */
+enum mixer_ctl_type mixer_ctl_get_type(const struct mixer_ctl *ctl)
+{
+ if (!ctl)
+ return MIXER_CTL_TYPE_UNKNOWN;
+
+ switch (ctl->info.type) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN: return MIXER_CTL_TYPE_BOOL;
+ case SNDRV_CTL_ELEM_TYPE_INTEGER: return MIXER_CTL_TYPE_INT;
+ case SNDRV_CTL_ELEM_TYPE_ENUMERATED: return MIXER_CTL_TYPE_ENUM;
+ case SNDRV_CTL_ELEM_TYPE_BYTES: return MIXER_CTL_TYPE_BYTE;
+ case SNDRV_CTL_ELEM_TYPE_IEC958: return MIXER_CTL_TYPE_IEC958;
+ case SNDRV_CTL_ELEM_TYPE_INTEGER64: return MIXER_CTL_TYPE_INT64;
+ default: return MIXER_CTL_TYPE_UNKNOWN;
+ };
+}
+
+/** Gets the string that describes the value type of the control.
+ * @param ctl An initialized control handle
+ * @returns On success, a string describing type of mixer control.
+ * @ingroup libtinyalsa-mixer
+ */
+const char *mixer_ctl_get_type_string(const struct mixer_ctl *ctl)
+{
+ if (!ctl)
+ return "";
+
+ switch (ctl->info.type) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN: return "BOOL";
+ case SNDRV_CTL_ELEM_TYPE_INTEGER: return "INT";
+ case SNDRV_CTL_ELEM_TYPE_ENUMERATED: return "ENUM";
+ case SNDRV_CTL_ELEM_TYPE_BYTES: return "BYTE";
+ case SNDRV_CTL_ELEM_TYPE_IEC958: return "IEC958";
+ case SNDRV_CTL_ELEM_TYPE_INTEGER64: return "INT64";
+ default: return "Unknown";
+ };
+}
+
+/** Gets the number of values in the control.
+ * @param ctl An initialized control handle
+ * @returns The number of values in the mixer control
+ * @ingroup libtinyalsa-mixer
+ */
+unsigned int mixer_ctl_get_num_values(const struct mixer_ctl *ctl)
+{
+ if (!ctl)
+ return 0;
+
+ return ctl->info.count;
+}
+
+static int percent_to_int(const struct snd_ctl_elem_info *ei, int percent)
+{
+ if ((percent > 100) || (percent < 0)) {
+ return -EINVAL;
+ }
+
+ int range = (ei->value.integer.max - ei->value.integer.min);
+
+ return ei->value.integer.min + (range * percent) / 100;
+}
+
+static int int_to_percent(const struct snd_ctl_elem_info *ei, int value)
+{
+ int range = (ei->value.integer.max - ei->value.integer.min);
+
+ if (range == 0)
+ return 0;
+
+ return ((value - ei->value.integer.min) * 100) / range;
+}
+
+/** Gets a percentage representation of a specified control value.
+ * @param ctl An initialized control handle.
+ * @param id The index of the value within the control.
+ * @returns On success, the percentage representation of the control value.
+ * On failure, -EINVAL is returned.
+ * @ingroup libtinyalsa-mixer
+ */
+int mixer_ctl_get_percent(const struct mixer_ctl *ctl, unsigned int id)
+{
+ if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER))
+ return -EINVAL;
+
+ return int_to_percent(&ctl->info, mixer_ctl_get_value(ctl, id));
+}
+
+/** Sets the value of a control by percent, specified by the value index.
+ * @param ctl An initialized control handle.
+ * @param id The index of the value to set
+ * @param percent A percentage value between 0 and 100.
+ * @returns On success, zero is returned.
+ * On failure, non-zero is returned.
+ * @ingroup libtinyalsa-mixer
+ */
+int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent)
+{
+ if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER))
+ return -EINVAL;
+
+ return mixer_ctl_set_value(ctl, id, percent_to_int(&ctl->info, percent));
+}
+
+/** Gets the value of a control.
+ * @param ctl An initialized control handle.
+ * @param id The index of the control value.
+ * @returns On success, the specified value is returned.
+ * On failure, -EINVAL is returned.
+ * @ingroup libtinyalsa-mixer
+ */
+int mixer_ctl_get_value(const struct mixer_ctl *ctl, unsigned int id)
+{
+ struct mixer_ctl_group *grp;
+ struct snd_ctl_elem_value ev;
+ int ret;
+
+ if (!ctl || (id >= ctl->info.count))
+ return -EINVAL;
+
+ grp = ctl->grp;
+ memset(&ev, 0, sizeof(ev));
+ ev.id.numid = ctl->info.id.numid;
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
+ if (ret < 0)
+ return ret;
+
+ switch (ctl->info.type) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+ return !!ev.value.integer.value[id];
+
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ return ev.value.integer.value[id];
+
+ case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+ return ev.value.enumerated.item[id];
+
+ case SNDRV_CTL_ELEM_TYPE_BYTES:
+ return ev.value.bytes.data[id];
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/** Gets the contents of a control's value array.
+ * @param ctl An initialized control handle.
+ * @param array A pointer to write the array data to.
+ * The size of this array must be equal to the number of items in the array
+ * multiplied by the size of each item.
+ * @param count The number of items in the array.
+ * This parameter must match the number of items in the control.
+ * The number of items in the control may be accessed via @ref mixer_ctl_get_num_values
+ * @returns On success, zero.
+ * On failure, non-zero.
+ * @ingroup libtinyalsa-mixer
+ */
+int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count)
+{
+ struct mixer_ctl_group *grp;
+ struct snd_ctl_elem_value ev;
+ int ret = 0;
+ size_t size;
+ void *source;
+
+ if (!ctl || !count || !array)
+ return -EINVAL;
+
+ grp = ctl->grp;
+
+ if (count > ctl->info.count)
+ return -EINVAL;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.id.numid = ctl->info.id.numid;
+
+ switch (ctl->info.type) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
+ if (ret < 0)
+ return ret;
+ size = sizeof(ev.value.integer.value[0]);
+ source = ev.value.integer.value;
+ break;
+
+ case SNDRV_CTL_ELEM_TYPE_BYTES:
+ /* check if this is new bytes TLV */
+ if (mixer_ctl_is_access_tlv_rw(ctl)) {
+ struct snd_ctl_tlv *tlv;
+ int ret;
+
+ if (count > SIZE_MAX - sizeof(*tlv))
+ return -EINVAL;
+
+ tlv = calloc(1, sizeof(*tlv) + count);
+ if (!tlv)
+ return -ENOMEM;
+
+ tlv->numid = ctl->info.id.numid;
+ tlv->length = count;
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_TLV_READ, tlv);
+
+ source = tlv->tlv;
+ memcpy(array, source, count);
+
+ free(tlv);
+
+ return ret;
+ } else {
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
+ if (ret < 0)
+ return ret;
+ size = sizeof(ev.value.bytes.data[0]);
+ source = ev.value.bytes.data;
+ break;
+ }
+
+ case SNDRV_CTL_ELEM_TYPE_IEC958:
+ size = sizeof(ev.value.iec958);
+ source = &ev.value.iec958;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ memcpy(array, source, size * count);
+
+ return 0;
+}
+
+/** Sets the value of a control, specified by the value index.
+ * @param ctl An initialized control handle.
+ * @param id The index of the value within the control.
+ * @param value The value to set.
+ * This must be in a range specified by @ref mixer_ctl_get_range_min
+ * and @ref mixer_ctl_get_range_max.
+ * @returns On success, zero is returned.
+ * On failure, non-zero is returned.
+ * @ingroup libtinyalsa-mixer
+ */
+int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)
+{
+ struct mixer_ctl_group *grp;
+ struct snd_ctl_elem_value ev;
+ int ret;
+
+ if (!ctl || (id >= ctl->info.count))
+ return -EINVAL;
+
+ grp = ctl->grp;
+ memset(&ev, 0, sizeof(ev));
+ ev.id.numid = ctl->info.id.numid;
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
+ if (ret < 0)
+ return ret;
+
+ switch (ctl->info.type) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+ ev.value.integer.value[id] = !!value;
+ break;
+
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ if ((value < mixer_ctl_get_range_min(ctl)) ||
+ (value > mixer_ctl_get_range_max(ctl))) {
+ return -EINVAL;
+ }
+
+ ev.value.integer.value[id] = value;
+ break;
+
+ case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+ ev.value.enumerated.item[id] = value;
+ break;
+
+ case SNDRV_CTL_ELEM_TYPE_BYTES:
+ ev.value.bytes.data[id] = value;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
+}
+
+/** Sets the contents of a control's value array.
+ * @param ctl An initialized control handle.
+ * @param array The array containing control values.
+ * @param count The number of values in the array.
+ * This must match the number of values in the control.
+ * The number of values in a control may be accessed via @ref mixer_ctl_get_num_values
+ * @returns On success, zero.
+ * On failure, non-zero.
+ * @ingroup libtinyalsa-mixer
+ */
+int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count)
+{
+ struct mixer_ctl_group *grp;
+ struct snd_ctl_elem_value ev;
+ size_t size;
+ void *dest;
+
+ if ((!ctl) || !count || !array)
+ return -EINVAL;
+
+ grp = ctl->grp;
+
+ if (count > ctl->info.count)
+ return -EINVAL;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.id.numid = ctl->info.id.numid;
+
+ switch (ctl->info.type) {
+ case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ size = sizeof(ev.value.integer.value[0]);
+ dest = ev.value.integer.value;
+ break;
+
+ case SNDRV_CTL_ELEM_TYPE_BYTES:
+ /* check if this is new bytes TLV */
+ if (mixer_ctl_is_access_tlv_rw(ctl)) {
+ struct snd_ctl_tlv *tlv;
+ int ret = 0;
+
+ if (count > SIZE_MAX - sizeof(*tlv))
+ return -EINVAL;
+
+ tlv = calloc(1, sizeof(*tlv) + count);
+ if (!tlv)
+ return -ENOMEM;
+
+ tlv->numid = ctl->info.id.numid;
+ tlv->length = count;
+ memcpy(tlv->tlv, array, count);
+
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_TLV_WRITE, tlv);
+ free(tlv);
+
+ return ret;
+ } else {
+ size = sizeof(ev.value.bytes.data[0]);
+ dest = ev.value.bytes.data;
+ }
+ break;
+
+ case SNDRV_CTL_ELEM_TYPE_IEC958:
+ size = sizeof(ev.value.iec958);
+ dest = &ev.value.iec958;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ memcpy(dest, array, size * count);
+
+ return grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
+}
+
+/** Gets the minimum value of an control.
+ * The control must have an integer type.
+ * The type of the control can be checked with @ref mixer_ctl_get_type.
+ * @param ctl An initialized control handle.
+ * @returns On success, the minimum value of the control.
+ * On failure, -EINVAL.
+ * @ingroup libtinyalsa-mixer
+ */
+int mixer_ctl_get_range_min(const struct mixer_ctl *ctl)
+{
+ if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER))
+ return -EINVAL;
+
+ return ctl->info.value.integer.min;
+}
+
+/** Gets the maximum value of an control.
+ * The control must have an integer type.
+ * The type of the control can be checked with @ref mixer_ctl_get_type.
+ * @param ctl An initialized control handle.
+ * @returns On success, the maximum value of the control.
+ * On failure, -EINVAL.
+ * @ingroup libtinyalsa-mixer
+ */
+int mixer_ctl_get_range_max(const struct mixer_ctl *ctl)
+{
+ if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER))
+ return -EINVAL;
+
+ return ctl->info.value.integer.max;
+}
+
+/** Get the number of enumerated items in the control.
+ * @param ctl An initialized control handle.
+ * @returns The number of enumerated items in the control.
+ * @ingroup libtinyalsa-mixer
+ */
+unsigned int mixer_ctl_get_num_enums(const struct mixer_ctl *ctl)
+{
+ if (!ctl)
+ return 0;
+
+ return ctl->info.value.enumerated.items;
+}
+
+int mixer_ctl_fill_enum_string(struct mixer_ctl *ctl)
+{
+ struct mixer_ctl_group *grp = ctl->grp;
+ struct snd_ctl_elem_info tmp;
+ unsigned int m;
+ char **enames;
+
+ if (ctl->ename) {
+ return 0;
+ }
+
+ enames = calloc(ctl->info.value.enumerated.items, sizeof(char*));
+ if (!enames)
+ goto fail;
+ for (m = 0; m < ctl->info.value.enumerated.items; m++) {
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.id.numid = ctl->info.id.numid;
+ tmp.value.enumerated.item = m;
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0)
+ goto fail;
+ enames[m] = strdup(tmp.value.enumerated.name);
+ if (!enames[m])
+ goto fail;
+ }
+ ctl->ename = enames;
+ return 0;
+
+fail:
+ if (enames) {
+ for (m = 0; m < ctl->info.value.enumerated.items; m++) {
+ if (enames[m]) {
+ free(enames[m]);
+ }
+ }
+ free(enames);
+ }
+ return -1;
+}
+
+/** Gets the string representation of an enumerated item.
+ * @param ctl An initialized control handle.
+ * @param enum_id The index of the enumerated value.
+ * @returns A string representation of the enumerated item.
+ * @ingroup libtinyalsa-mixer
+ */
+const char *mixer_ctl_get_enum_string(struct mixer_ctl *ctl,
+ unsigned int enum_id)
+{
+ if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_ENUMERATED) ||
+ (enum_id >= ctl->info.value.enumerated.items) ||
+ mixer_ctl_fill_enum_string(ctl) != 0)
+ return NULL;
+
+ return (const char *)ctl->ename[enum_id];
+}
+
+/** Set an enumeration value by string value.
+ * @param ctl An enumerated mixer control.
+ * @param string The string representation of an enumeration.
+ * @returns On success, zero.
+ * On failure, zero.
+ * @ingroup libtinyalsa-mixer
+ */
+int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string)
+{
+ struct mixer_ctl_group *grp;
+ unsigned int i, num_enums;
+ struct snd_ctl_elem_value ev;
+ int ret;
+
+ if (!ctl || (ctl->info.type != SNDRV_CTL_ELEM_TYPE_ENUMERATED) ||
+ mixer_ctl_fill_enum_string(ctl) != 0)
+ return -EINVAL;
+
+ grp = ctl->grp;
+ num_enums = ctl->info.value.enumerated.items;
+ for (i = 0; i < num_enums; i++) {
+ if (!strcmp(string, ctl->ename[i])) {
+ memset(&ev, 0, sizeof(ev));
+ ev.value.enumerated.item[0] = i;
+ ev.id.numid = ctl->info.id.numid;
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
+ if (ret < 0)
+ return ret;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
diff --git a/src/mixer_hw.c b/src/mixer_hw.c
new file mode 100644
index 0000000..da5a390
--- /dev/null
+++ b/src/mixer_hw.c
@@ -0,0 +1,121 @@
+/* mixer_hw.c
+** Copyright (c) 2019, The Linux Foundation.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <poll.h>
+
+#include <sys/ioctl.h>
+
+#include <linux/ioctl.h>
+#include <sound/asound.h>
+
+#include "mixer_io.h"
+
+/** Store the hardware (kernel interface) mixer data */
+struct mixer_hw_data {
+ /* Card number for the mixer */
+ unsigned int card;
+ /* File descriptor of the mixer device node */
+ int fd;
+};
+
+static void mixer_hw_close(void *data)
+{
+ struct mixer_hw_data *hw_data = data;
+
+ if (!hw_data)
+ return;
+
+ if (hw_data->fd >= 0)
+ close(hw_data->fd);
+
+ hw_data->fd = -1;
+ free(hw_data);
+ hw_data = NULL;
+}
+
+static int mixer_hw_ioctl(void *data, unsigned int cmd, ...)
+{
+ struct mixer_hw_data *hw_data = data;
+ va_list ap;
+ void *arg;
+
+ va_start(ap, cmd);
+ arg = va_arg(ap, void *);
+ va_end(ap);
+
+ return ioctl(hw_data->fd, cmd, arg);
+}
+
+static ssize_t mixer_hw_read_event(void *data, struct snd_ctl_event *ev,
+ size_t size)
+{
+ struct mixer_hw_data *hw_data = data;
+
+ return read(hw_data->fd, ev, size);
+}
+
+static const struct mixer_ops mixer_hw_ops = {
+ .close = mixer_hw_close,
+ .ioctl = mixer_hw_ioctl,
+ .read_event = mixer_hw_read_event,
+};
+
+int mixer_hw_open(unsigned int card, void **data,
+ const struct mixer_ops **ops)
+{
+ struct mixer_hw_data *hw_data;
+ int fd;
+ char fn[256];
+
+ snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);
+ fd = open(fn, O_RDWR);
+ if (fd < 0)
+ return fd;
+
+ hw_data = calloc(1, sizeof(*hw_data));
+ if (!hw_data)
+ return -1;
+
+ hw_data->card = card;
+ hw_data->fd = fd;
+ *data = hw_data;
+ *ops = &mixer_hw_ops;
+
+ return fd;
+}
diff --git a/src/mixer_io.h b/src/mixer_io.h
new file mode 100644
index 0000000..bb3bc44
--- /dev/null
+++ b/src/mixer_io.h
@@ -0,0 +1,51 @@
+/* mixer_io.h
+** Copyright (c) 2019, The Linux Foundation.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+#ifndef TINYALSA_SRC_MIXER_H
+#define TINYALSA_SRC_MIXER_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sound/asound.h>
+
+struct mixer_ops;
+
+int mixer_hw_open(unsigned int card, void **data,
+ const struct mixer_ops **ops);
+int mixer_plugin_open(unsigned int card, void **data,
+ const struct mixer_ops **ops);
+
+struct mixer_ops {
+ void (*close) (void *data);
+ int (*get_poll_fd) (void *data, struct pollfd *pfd, int count);
+ ssize_t (*read_event) (void *data, struct snd_ctl_event *ev, size_t size);
+ int (*ioctl) (void *data, unsigned int cmd, ...);
+};
+
+#endif /* TINYALSA_SRC_MIXER_H */
diff --git a/src/mixer_plugin.c b/src/mixer_plugin.c
new file mode 100644
index 0000000..34117a9
--- /dev/null
+++ b/src/mixer_plugin.c
@@ -0,0 +1,475 @@
+/* mixer_plugin.c
+** Copyright (c) 2019, The Linux Foundation.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+#include <tinyalsa/plugin.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <poll.h>
+#include <dlfcn.h>
+#include <string.h>
+#include <sys/eventfd.h>
+#include <sys/ioctl.h>
+
+#include <linux/ioctl.h>
+#include <sound/asound.h>
+
+#include "snd_card_plugin.h"
+#include "mixer_io.h"
+
+/** Encapulates the mixer plugin specific data */
+struct mixer_plug_data {
+ /** Card number associated with the plugin */
+ int card;
+ /** Device node for mixer */
+ void *mixer_node;
+ /** Pointer to the plugin's ops */
+ const struct mixer_plugin_ops *ops;
+ /** Pointer to plugin responsible to service the controls */
+ struct mixer_plugin *plugin;
+ /** Handle to the plugin library */
+ void *dl_hdl;
+};
+
+static int mixer_plug_get_elem_id(struct mixer_plug_data *plug_data,
+ struct snd_ctl_elem_id *id, unsigned int offset)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+
+ if (offset >= plugin->num_controls) {
+ fprintf(stderr, "%s: invalid offset %u\n",
+ __func__, offset);
+ return -EINVAL;
+ }
+
+ ctl = plugin->controls + offset;
+ id->numid = offset;
+ id->iface = ctl->iface;
+
+ strncpy((char *)id->name, (char *)ctl->name,
+ sizeof(id->name));
+
+ return 0;
+}
+
+static int mixer_plug_info_enum(struct snd_control *ctl,
+ struct snd_ctl_elem_info *einfo)
+{
+ struct snd_value_enum *val = ctl->value;
+
+ einfo->count = 1;
+ einfo->value.enumerated.items = val->items;
+
+ if (einfo->value.enumerated.item >= val->items)
+ return -EINVAL;
+
+ strncpy(einfo->value.enumerated.name,
+ val->texts[einfo->value.enumerated.item],
+ sizeof(einfo->value.enumerated.name));
+
+ return 0;
+}
+
+static int mixer_plug_info_bytes(struct snd_control *ctl,
+ struct snd_ctl_elem_info *einfo)
+{
+ struct snd_value_bytes *val;
+ struct snd_value_tlv_bytes *val_tlv;
+
+ if (ctl->access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) {
+ val_tlv = ctl->value;
+ einfo->count = val_tlv->size;
+ } else {
+ val = ctl->value;
+ einfo->count = val->size;
+ }
+
+ return 0;
+}
+
+static int mixer_plug_info_integer(struct snd_control *ctl,
+ struct snd_ctl_elem_info *einfo)
+{
+ struct snd_value_int *val = ctl->value;
+
+ einfo->count = val->count;
+ einfo->value.integer.min = val->min;
+ einfo->value.integer.max = val->max;
+ einfo->value.integer.step = val->step;
+
+ return 0;
+}
+
+void mixer_plug_notifier_cb(struct mixer_plugin *plugin)
+{
+ plugin->event_cnt++;
+ eventfd_write(plugin->eventfd, 1);
+}
+
+/* In consume_event/read, do not call eventfd_read until all events are read from list.
+ This will make poll getting unblocked until all events are read */
+static ssize_t mixer_plug_read_event(void *data, struct snd_ctl_event *ev, size_t size)
+{
+ struct mixer_plug_data *plug_data = data;
+ struct mixer_plugin *plugin = plug_data->plugin;
+ eventfd_t evfd;
+ ssize_t result = 0;
+
+ result = plug_data->ops->read_event(plugin, ev, size);
+
+ if (result > 0) {
+ plugin->event_cnt -= result / sizeof(struct snd_ctl_event);
+ if (plugin->event_cnt == 0)
+ eventfd_read(plugin->eventfd, &evfd);
+ }
+
+ return result;
+}
+
+static int mixer_plug_subscribe_events(struct mixer_plug_data *plug_data,
+ int *subscribe)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ eventfd_t evfd;
+
+ if (*subscribe < 0 || *subscribe > 1) {
+ *subscribe = plugin->subscribed;
+ return -EINVAL;
+ }
+
+ if (*subscribe && !plugin->subscribed) {
+ plug_data->ops->subscribe_events(plugin, &mixer_plug_notifier_cb);
+ } else if (plugin->subscribed && !*subscribe) {
+ plug_data->ops->subscribe_events(plugin, NULL);
+
+ if (plugin->event_cnt)
+ eventfd_read(plugin->eventfd, &evfd);
+
+ plugin->event_cnt = 0;
+ }
+
+ plugin->subscribed = *subscribe;
+ return 0;
+}
+
+static int mixer_plug_get_poll_fd(void *data, struct pollfd *pfd, int count)
+{
+ struct mixer_plug_data *plug_data = data;
+ struct mixer_plugin *plugin = plug_data->plugin;
+
+ if (plugin->eventfd != -1) {
+ pfd[count].fd = plugin->eventfd;
+ return 0;
+ }
+ return -ENODEV;
+}
+
+static int mixer_plug_tlv_write(struct mixer_plug_data *plug_data,
+ struct snd_ctl_tlv *tlv)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+ struct snd_value_tlv_bytes *val_tlv;
+
+ ctl = plugin->controls + tlv->numid;
+ val_tlv = ctl->value;
+
+ return val_tlv->put(plugin, ctl, tlv);
+}
+
+static int mixer_plug_tlv_read(struct mixer_plug_data *plug_data,
+ struct snd_ctl_tlv *tlv)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+ struct snd_value_tlv_bytes *val_tlv;
+
+ ctl = plugin->controls + tlv->numid;
+ val_tlv = ctl->value;
+
+ return val_tlv->get(plugin, ctl, tlv);
+}
+
+static int mixer_plug_elem_write(struct mixer_plug_data *plug_data,
+ struct snd_ctl_elem_value *ev)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+ int ret;
+
+ ret = mixer_plug_get_elem_id(plug_data, &ev->id, ev->id.numid);
+ if (ret < 0)
+ return ret;
+
+ ctl = plugin->controls + ev->id.numid;
+
+ return ctl->put(plugin, ctl, ev);
+}
+
+static int mixer_plug_elem_read(struct mixer_plug_data *plug_data,
+ struct snd_ctl_elem_value *ev)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+ int ret;
+
+ ret = mixer_plug_get_elem_id(plug_data, &ev->id, ev->id.numid);
+ if (ret < 0)
+ return ret;
+
+ ctl = plugin->controls + ev->id.numid;
+
+ return ctl->get(plugin, ctl, ev);
+
+}
+
+static int mixer_plug_get_elem_info(struct mixer_plug_data *plug_data,
+ struct snd_ctl_elem_info *einfo)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+ int ret;
+
+ ret = mixer_plug_get_elem_id(plug_data, &einfo->id,
+ einfo->id.numid);
+ if (ret < 0)
+ return ret;
+
+ ctl = plugin->controls + einfo->id.numid;
+ einfo->type = ctl->type;
+ einfo->access = ctl->access;
+
+ switch (einfo->type) {
+ case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+ ret = mixer_plug_info_enum(ctl, einfo);
+ if (ret < 0)
+ return ret;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_BYTES:
+ ret = mixer_plug_info_bytes(ctl, einfo);
+ if (ret < 0)
+ return ret;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ ret = mixer_plug_info_integer(ctl, einfo);
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ fprintf(stderr,"%s: unknown type %d\n",
+ __func__, einfo->type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mixer_plug_get_elem_list(struct mixer_plug_data *plug_data,
+ struct snd_ctl_elem_list *elist)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ unsigned int avail;
+ struct snd_ctl_elem_id *id;
+ int ret;
+
+ elist->count = plugin->num_controls;
+ elist->used = 0;
+ avail = elist->space;
+
+ while (avail > 0) {
+ id = elist->pids + elist->used;
+ ret = mixer_plug_get_elem_id(plug_data, id, elist->used);
+ if (ret < 0)
+ return ret;
+
+ avail--;
+ elist->used++;
+ }
+
+ return 0;
+}
+
+static int mixer_plug_get_card_info(struct mixer_plug_data *plug_data,
+ struct snd_ctl_card_info *card_info)
+{
+ /*TODO: Fill card_info here from snd-card-def */
+ memset(card_info, 0, sizeof(*card_info));
+ card_info->card = plug_data->card;
+
+ return 0;
+}
+
+static void mixer_plug_close(void *data)
+{
+ struct mixer_plug_data *plug_data = data;
+ struct mixer_plugin *plugin = plug_data->plugin;
+ eventfd_t evfd;
+
+ if (plugin->event_cnt)
+ eventfd_read(plugin->eventfd, &evfd);
+
+ plug_data->ops->close(&plugin);
+ dlclose(plug_data->dl_hdl);
+
+ free(plug_data);
+ plug_data = NULL;
+}
+
+static int mixer_plug_ioctl(void *data, unsigned int cmd, ...)
+{
+ struct mixer_plug_data *plug_data = data;
+ int ret;
+ va_list ap;
+ void *arg;
+
+ va_start(ap, cmd);
+ arg = va_arg(ap, void *);
+ va_end(ap);
+
+ switch (cmd) {
+ case SNDRV_CTL_IOCTL_CARD_INFO:
+ ret = mixer_plug_get_card_info(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_ELEM_LIST:
+ ret = mixer_plug_get_elem_list(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_ELEM_INFO:
+ ret = mixer_plug_get_elem_info(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_ELEM_READ:
+ ret = mixer_plug_elem_read(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_ELEM_WRITE:
+ ret = mixer_plug_elem_write(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_TLV_READ:
+ ret = mixer_plug_tlv_read(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_TLV_WRITE:
+ ret = mixer_plug_tlv_write(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
+ ret = mixer_plug_subscribe_events(plug_data, arg);
+ break;
+ default:
+ /* TODO: plugin should support ioctl */
+ ret = -EFAULT;
+ break;
+ }
+
+ return ret;
+}
+
+static const struct mixer_ops mixer_plug_ops = {
+ .close = mixer_plug_close,
+ .read_event = mixer_plug_read_event,
+ .get_poll_fd = mixer_plug_get_poll_fd,
+ .ioctl = mixer_plug_ioctl,
+};
+
+int mixer_plugin_open(unsigned int card, void **data,
+ const struct mixer_ops **ops)
+{
+ struct mixer_plug_data *plug_data;
+ struct mixer_plugin *plugin = NULL;
+ void *dl_hdl;
+ char *so_name;
+ int ret;
+
+ plug_data = calloc(1, sizeof(*plug_data));
+ if (!plug_data)
+ return -ENOMEM;
+
+ plug_data->mixer_node = snd_utils_open_mixer(card);
+ if (!plug_data->mixer_node) {
+ /* Do not print error here.
+ * It is valid for card to not have virtual mixer node
+ */
+ goto err_get_mixer_node;
+ }
+
+ ret = snd_utils_get_str(plug_data->mixer_node, "so-name",
+ &so_name);
+ if(ret) {
+ fprintf(stderr, "%s: mixer so-name not found for card %u\n",
+ __func__, card);
+ goto err_get_lib_name;
+
+ }
+
+ dl_hdl = dlopen(so_name, RTLD_NOW);
+ if (!dl_hdl) {
+ fprintf(stderr, "%s: unable to open %s\n",
+ __func__, so_name);
+ goto err_dlopen;
+ }
+
+ dlerror();
+ plug_data->ops = dlsym(dl_hdl, "mixer_plugin_ops");
+ if (!plug_data->ops) {
+ fprintf(stderr, "%s: dlsym open fn failed: %s\n",
+ __func__, dlerror());
+ goto err_ops;
+ }
+
+ ret = plug_data->ops->open(&plugin, card);
+ if (ret) {
+ fprintf(stderr, "%s: failed to open plugin, err: %d\n",
+ __func__, ret);
+ goto err_ops;
+ }
+
+ plug_data->plugin = plugin;
+ plug_data->card = card;
+ plug_data->dl_hdl = dl_hdl;
+ plugin->eventfd = eventfd(0, 0);
+
+ *data = plug_data;
+ *ops = &mixer_plug_ops;
+
+ return 0;
+
+err_ops:
+ dlclose(dl_hdl);
+err_dlopen:
+err_get_lib_name:
+ snd_utils_close_dev_node(plug_data->mixer_node);
+err_get_mixer_node:
+ free(plug_data);
+ return -1;
+}
diff --git a/src/pcm.c b/src/pcm.c
new file mode 100644
index 0000000..98ca9eb
--- /dev/null
+++ b/src/pcm.c
@@ -0,0 +1,1770 @@
+/* pcm.c
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <poll.h>
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <time.h>
+#include <limits.h>
+
+#include <linux/ioctl.h>
+
+#ifndef __force
+#define __force
+#endif
+
+#ifndef __bitwise
+#define __bitwise
+#endif
+
+#ifndef __user
+#define __user
+#endif
+
+#include <sound/asound.h>
+
+#include <tinyalsa/pcm.h>
+#include <tinyalsa/limits.h>
+#include "pcm_io.h"
+#include "snd_card_plugin.h"
+
+#ifndef PARAM_MAX
+#define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL
+#endif /* PARAM_MAX */
+
+#ifndef SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP
+#define SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP (1<<2)
+#endif /* SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP */
+
+/* Logs information into a string; follows snprintf() in that
+ * offset may be greater than size, and though no characters are copied
+ * into string, characters are still counted into offset. */
+#define STRLOG(string, offset, size, ...) \
+ do { int temp, clipoffset = offset > size ? size : offset; \
+ temp = snprintf(string + clipoffset, size - clipoffset, __VA_ARGS__); \
+ if (temp > 0) offset += temp; } while (0)
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+/* refer to SNDRV_PCM_ACCESS_##index in sound/asound.h. */
+static const char * const access_lookup[] = {
+ "MMAP_INTERLEAVED",
+ "MMAP_NONINTERLEAVED",
+ "MMAP_COMPLEX",
+ "RW_INTERLEAVED",
+ "RW_NONINTERLEAVED",
+};
+
+/* refer to SNDRV_PCM_FORMAT_##index in sound/asound.h. */
+static const char * const format_lookup[] = {
+ /*[0] =*/ "S8",
+ "U8",
+ "S16_LE",
+ "S16_BE",
+ "U16_LE",
+ "U16_BE",
+ "S24_LE",
+ "S24_BE",
+ "U24_LE",
+ "U24_BE",
+ "S32_LE",
+ "S32_BE",
+ "U32_LE",
+ "U32_BE",
+ "FLOAT_LE",
+ "FLOAT_BE",
+ "FLOAT64_LE",
+ "FLOAT64_BE",
+ "IEC958_SUBFRAME_LE",
+ "IEC958_SUBFRAME_BE",
+ "MU_LAW",
+ "A_LAW",
+ "IMA_ADPCM",
+ "MPEG",
+ /*[24] =*/ "GSM",
+ /* gap */
+ [31] = "SPECIAL",
+ "S24_3LE",
+ "S24_3BE",
+ "U24_3LE",
+ "U24_3BE",
+ "S20_3LE",
+ "S20_3BE",
+ "U20_3LE",
+ "U20_3BE",
+ "S18_3LE",
+ "S18_3BE",
+ "U18_3LE",
+ /*[43] =*/ "U18_3BE",
+#if 0
+ /* recent additions, may not be present on local asound.h */
+ "G723_24",
+ "G723_24_1B",
+ "G723_40",
+ "G723_40_1B",
+ "DSD_U8",
+ "DSD_U16_LE",
+#endif
+};
+
+/* refer to SNDRV_PCM_SUBFORMAT_##index in sound/asound.h. */
+static const char * const subformat_lookup[] = {
+ "STD",
+};
+
+static inline int param_is_mask(int p)
+{
+ return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) &&
+ (p <= SNDRV_PCM_HW_PARAM_LAST_MASK);
+}
+
+static inline int param_is_interval(int p)
+{
+ return (p >= SNDRV_PCM_HW_PARAM_FIRST_INTERVAL) &&
+ (p <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL);
+}
+
+static inline const struct snd_interval *param_get_interval(const struct snd_pcm_hw_params *p, int n)
+{
+ return &(p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]);
+}
+
+static inline struct snd_interval *param_to_interval(struct snd_pcm_hw_params *p, int n)
+{
+ return &(p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]);
+}
+
+static inline struct snd_mask *param_to_mask(struct snd_pcm_hw_params *p, int n)
+{
+ return &(p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK]);
+}
+
+static void param_set_mask(struct snd_pcm_hw_params *p, int n, unsigned int bit)
+{
+ if (bit >= SNDRV_MASK_MAX)
+ return;
+ if (param_is_mask(n)) {
+ struct snd_mask *m = param_to_mask(p, n);
+ m->bits[0] = 0;
+ m->bits[1] = 0;
+ m->bits[bit >> 5] |= (1 << (bit & 31));
+ }
+}
+
+static void param_set_min(struct snd_pcm_hw_params *p, int n, unsigned int val)
+{
+ if (param_is_interval(n)) {
+ struct snd_interval *i = param_to_interval(p, n);
+ i->min = val;
+ }
+}
+
+static unsigned int param_get_min(const struct snd_pcm_hw_params *p, int n)
+{
+ if (param_is_interval(n)) {
+ const struct snd_interval *i = param_get_interval(p, n);
+ return i->min;
+ }
+ return 0;
+}
+
+static unsigned int param_get_max(const struct snd_pcm_hw_params *p, int n)
+{
+ if (param_is_interval(n)) {
+ const struct snd_interval *i = param_get_interval(p, n);
+ return i->max;
+ }
+ return 0;
+}
+
+static void param_set_int(struct snd_pcm_hw_params *p, int n, unsigned int val)
+{
+ if (param_is_interval(n)) {
+ struct snd_interval *i = param_to_interval(p, n);
+ i->min = val;
+ i->max = val;
+ i->integer = 1;
+ }
+}
+
+static unsigned int param_get_int(struct snd_pcm_hw_params *p, int n)
+{
+ if (param_is_interval(n)) {
+ struct snd_interval *i = param_to_interval(p, n);
+ if (i->integer)
+ return i->max;
+ }
+ return 0;
+}
+
+static void param_init(struct snd_pcm_hw_params *p)
+{
+ int n;
+
+ memset(p, 0, sizeof(*p));
+ for (n = SNDRV_PCM_HW_PARAM_FIRST_MASK;
+ n <= SNDRV_PCM_HW_PARAM_LAST_MASK; n++) {
+ struct snd_mask *m = param_to_mask(p, n);
+ m->bits[0] = ~0;
+ m->bits[1] = ~0;
+ }
+ for (n = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL;
+ n <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; n++) {
+ struct snd_interval *i = param_to_interval(p, n);
+ i->min = 0;
+ i->max = ~0;
+ }
+ p->rmask = ~0U;
+ p->cmask = 0;
+ p->info = ~0U;
+}
+
+static unsigned int pcm_format_to_alsa(enum pcm_format format)
+{
+ switch (format) {
+
+ case PCM_FORMAT_S8:
+ return SNDRV_PCM_FORMAT_S8;
+
+ default:
+ case PCM_FORMAT_S16_LE:
+ return SNDRV_PCM_FORMAT_S16_LE;
+ case PCM_FORMAT_S16_BE:
+ return SNDRV_PCM_FORMAT_S16_BE;
+
+ case PCM_FORMAT_S24_LE:
+ return SNDRV_PCM_FORMAT_S24_LE;
+ case PCM_FORMAT_S24_BE:
+ return SNDRV_PCM_FORMAT_S24_BE;
+
+ case PCM_FORMAT_S24_3LE:
+ return SNDRV_PCM_FORMAT_S24_3LE;
+ case PCM_FORMAT_S24_3BE:
+ return SNDRV_PCM_FORMAT_S24_3BE;
+
+ case PCM_FORMAT_S32_LE:
+ return SNDRV_PCM_FORMAT_S32_LE;
+ case PCM_FORMAT_S32_BE:
+ return SNDRV_PCM_FORMAT_S32_BE;
+ };
+}
+
+#define PCM_ERROR_MAX 128
+
+/** A PCM handle.
+ * @ingroup libtinyalsa-pcm
+ */
+struct pcm {
+ /** The PCM's file descriptor */
+ int fd;
+ /** Flags that were passed to @ref pcm_open */
+ unsigned int flags;
+ /** The number of (under/over)runs that have occured */
+ int xruns;
+ /** Size of the buffer */
+ unsigned int buffer_size;
+ /** The boundary for ring buffer pointers */
+ unsigned int boundary;
+ /** Description of the last error that occured */
+ char error[PCM_ERROR_MAX];
+ /** Configuration that was passed to @ref pcm_open */
+ struct pcm_config config;
+ struct snd_pcm_mmap_status *mmap_status;
+ struct snd_pcm_mmap_control *mmap_control;
+ struct snd_pcm_sync_ptr *sync_ptr;
+ void *mmap_buffer;
+ unsigned int noirq_frames_per_msec;
+ /** The delay of the PCM, in terms of frames */
+ long pcm_delay;
+ /** The subdevice corresponding to the PCM */
+ unsigned int subdevice;
+ /** Pointer to the pcm ops, either hw or plugin */
+ const struct pcm_ops *ops;
+ /** Private data for pcm_hw or pcm_plugin */
+ void *data;
+ /** Pointer to the pcm node from snd card definition */
+ struct snd_node *snd_node;
+};
+
+static int oops(struct pcm *pcm, int e, const char *fmt, ...)
+{
+ va_list ap;
+ int sz;
+
+ va_start(ap, fmt);
+ vsnprintf(pcm->error, PCM_ERROR_MAX, fmt, ap);
+ va_end(ap);
+ sz = strlen(pcm->error);
+
+ if (e)
+ snprintf(pcm->error + sz, PCM_ERROR_MAX - sz,
+ ": %s", strerror(e));
+ return -1;
+}
+
+/** Gets the buffer size of the PCM.
+ * @param pcm A PCM handle.
+ * @return The buffer size of the PCM.
+ * @ingroup libtinyalsa-pcm
+ */
+unsigned int pcm_get_buffer_size(const struct pcm *pcm)
+{
+ return pcm->buffer_size;
+}
+
+/** Gets the channel count of the PCM.
+ * @param pcm A PCM handle.
+ * @return The channel count of the PCM.
+ * @ingroup libtinyalsa-pcm
+ */
+unsigned int pcm_get_channels(const struct pcm *pcm)
+{
+ return pcm->config.channels;
+}
+
+/** Gets the PCM configuration.
+ * @param pcm A PCM handle.
+ * @return The PCM configuration.
+ * This function only returns NULL if
+ * @p pcm is NULL.
+ * @ingroup libtinyalsa-pcm
+ * */
+const struct pcm_config * pcm_get_config(const struct pcm *pcm)
+{
+ if (pcm == NULL)
+ return NULL;
+ return &pcm->config;
+}
+
+/** Gets the rate of the PCM.
+ * The rate is given in frames per second.
+ * @param pcm A PCM handle.
+ * @return The rate of the PCM.
+ * @ingroup libtinyalsa-pcm
+ */
+unsigned int pcm_get_rate(const struct pcm *pcm)
+{
+ return pcm->config.rate;
+}
+
+/** Gets the format of the PCM.
+ * @param pcm A PCM handle.
+ * @return The format of the PCM.
+ * @ingroup libtinyalsa-pcm
+ */
+enum pcm_format pcm_get_format(const struct pcm *pcm)
+{
+ return pcm->config.format;
+}
+
+/** Gets the file descriptor of the PCM.
+ * Useful for extending functionality of the PCM when needed.
+ * @param pcm A PCM handle.
+ * @return The file descriptor of the PCM.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_get_file_descriptor(const struct pcm *pcm)
+{
+ return pcm->fd;
+}
+
+/** Gets the error message for the last error that occured.
+ * If no error occured and this function is called, the results are undefined.
+ * @param pcm A PCM handle.
+ * @return The error message of the last error that occured.
+ * @ingroup libtinyalsa-pcm
+ */
+const char* pcm_get_error(const struct pcm *pcm)
+{
+ return pcm->error;
+}
+
+/** Sets the PCM configuration.
+ * @param pcm A PCM handle.
+ * @param config The configuration to use for the
+ * PCM. This parameter may be NULL, in which case
+ * the default configuration is used.
+ * @returns Zero on success, a negative errno value
+ * on failure.
+ * @ingroup libtinyalsa-pcm
+ * */
+int pcm_set_config(struct pcm *pcm, const struct pcm_config *config)
+{
+ if (pcm == NULL)
+ return -EFAULT;
+ else if (config == NULL) {
+ config = &pcm->config;
+ pcm->config.channels = 2;
+ pcm->config.rate = 48000;
+ pcm->config.period_size = 1024;
+ pcm->config.period_count = 4;
+ pcm->config.format = PCM_FORMAT_S16_LE;
+ pcm->config.start_threshold = config->period_count * config->period_size;
+ pcm->config.stop_threshold = config->period_count * config->period_size;
+ pcm->config.silence_threshold = 0;
+ pcm->config.silence_size = 0;
+ } else
+ pcm->config = *config;
+
+ struct snd_pcm_hw_params params;
+ param_init(&params);
+ param_set_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT,
+ pcm_format_to_alsa(config->format));
+ param_set_min(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
+ param_set_int(&params, SNDRV_PCM_HW_PARAM_CHANNELS,
+ config->channels);
+ param_set_int(&params, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
+ param_set_int(&params, SNDRV_PCM_HW_PARAM_RATE, config->rate);
+
+ if (pcm->flags & PCM_NOIRQ) {
+
+ if (!(pcm->flags & PCM_MMAP)) {
+ oops(pcm, EINVAL, "noirq only currently supported with mmap().");
+ return -EINVAL;
+ }
+
+ params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP;
+ pcm->noirq_frames_per_msec = config->rate / 1000;
+ }
+
+ if (pcm->flags & PCM_MMAP)
+ param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
+ SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
+ else
+ param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
+ SNDRV_PCM_ACCESS_RW_INTERLEAVED);
+
+ if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {
+ int errno_copy = errno;
+ oops(pcm, errno, "cannot set hw params");
+ return -errno_copy;
+ }
+
+ /* get our refined hw_params */
+ pcm->config.period_size = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
+ pcm->config.period_count = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIODS);
+ pcm->buffer_size = config->period_count * config->period_size;
+
+ if (pcm->flags & PCM_MMAP) {
+ pcm->mmap_buffer = pcm->ops->mmap(pcm->data, NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
+ PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+ if (pcm->mmap_buffer == MAP_FAILED) {
+ int errno_copy = errno;
+ oops(pcm, errno, "failed to mmap buffer %d bytes\n",
+ pcm_frames_to_bytes(pcm, pcm->buffer_size));
+ return -errno_copy;
+ }
+ }
+
+ struct snd_pcm_sw_params sparams;
+ memset(&sparams, 0, sizeof(sparams));
+ sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;
+ sparams.period_step = 1;
+ sparams.avail_min = config->period_size;
+
+ if (!config->start_threshold) {
+ if (pcm->flags & PCM_IN)
+ pcm->config.start_threshold = sparams.start_threshold = 1;
+ else
+ pcm->config.start_threshold = sparams.start_threshold =
+ config->period_count * config->period_size / 2;
+ } else
+ sparams.start_threshold = config->start_threshold;
+
+ /* pick a high stop threshold - todo: does this need further tuning */
+ if (!config->stop_threshold) {
+ if (pcm->flags & PCM_IN)
+ pcm->config.stop_threshold = sparams.stop_threshold =
+ config->period_count * config->period_size * 10;
+ else
+ pcm->config.stop_threshold = sparams.stop_threshold =
+ config->period_count * config->period_size;
+ }
+ else
+ sparams.stop_threshold = config->stop_threshold;
+
+ sparams.xfer_align = config->period_size / 2; /* needed for old kernels */
+ sparams.silence_size = config->silence_size;
+ sparams.silence_threshold = config->silence_threshold;
+ pcm->boundary = sparams.boundary = pcm->buffer_size;
+
+ while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size)
+ pcm->boundary *= 2;
+
+ if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {
+ int errno_copy = errno;
+ oops(pcm, errno, "cannot set sw params");
+ return -errno_copy;
+ }
+
+ return 0;
+}
+
+/** Gets the subdevice on which the pcm has been opened.
+ * @param pcm A PCM handle.
+ * @return The subdevice on which the pcm has been opened */
+unsigned int pcm_get_subdevice(const struct pcm *pcm)
+{
+ return pcm->subdevice;
+}
+
+/** Determines the number of bits occupied by a @ref pcm_format.
+ * @param format A PCM format.
+ * @return The number of bits associated with @p format
+ * @ingroup libtinyalsa-pcm
+ */
+unsigned int pcm_format_to_bits(enum pcm_format format)
+{
+ switch (format) {
+ case PCM_FORMAT_S32_LE:
+ case PCM_FORMAT_S32_BE:
+ case PCM_FORMAT_S24_LE:
+ case PCM_FORMAT_S24_BE:
+ return 32;
+ case PCM_FORMAT_S24_3LE:
+ case PCM_FORMAT_S24_3BE:
+ return 24;
+ default:
+ case PCM_FORMAT_S16_LE:
+ case PCM_FORMAT_S16_BE:
+ return 16;
+ case PCM_FORMAT_S8:
+ return 8;
+ };
+}
+
+/** Determines how many frames of a PCM can fit into a number of bytes.
+ * @param pcm A PCM handle.
+ * @param bytes The number of bytes.
+ * @return The number of frames that may fit into @p bytes
+ * @ingroup libtinyalsa-pcm
+ */
+unsigned int pcm_bytes_to_frames(const struct pcm *pcm, unsigned int bytes)
+{
+ return bytes / (pcm->config.channels *
+ (pcm_format_to_bits(pcm->config.format) >> 3));
+}
+
+/** Determines how many bytes are occupied by a number of frames of a PCM.
+ * @param pcm A PCM handle.
+ * @param frames The number of frames of a PCM.
+ * @return The bytes occupied by @p frames.
+ * @ingroup libtinyalsa-pcm
+ */
+unsigned int pcm_frames_to_bytes(const struct pcm *pcm, unsigned int frames)
+{
+ return frames * pcm->config.channels *
+ (pcm_format_to_bits(pcm->config.format) >> 3);
+}
+
+static int pcm_sync_ptr(struct pcm *pcm, int flags)
+{
+ if (pcm->sync_ptr == NULL) {
+ /* status and control are mmaped */
+
+ if (flags & SNDRV_PCM_SYNC_PTR_HWSYNC) {
+ if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HWSYNC) == -1) {
+ oops(pcm, errno, "failed to sync hardware pointer");
+ return -1;
+ }
+ }
+ } else {
+ pcm->sync_ptr->flags = flags;
+ if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SYNC_PTR,
+ pcm->sync_ptr) < 0) {
+ oops(pcm, errno, "failed to sync mmap ptr");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int pcm_hw_mmap_status(struct pcm *pcm)
+{
+ if (pcm->sync_ptr)
+ return 0;
+
+ int page_size = sysconf(_SC_PAGE_SIZE);
+ pcm->mmap_status = pcm->ops->mmap(pcm->data, NULL, page_size, PROT_READ, MAP_SHARED,
+ SNDRV_PCM_MMAP_OFFSET_STATUS);
+ if (pcm->mmap_status == MAP_FAILED)
+ pcm->mmap_status = NULL;
+ if (!pcm->mmap_status)
+ goto mmap_error;
+
+ pcm->mmap_control = pcm->ops->mmap(pcm->data, NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_SHARED, SNDRV_PCM_MMAP_OFFSET_CONTROL);
+ if (pcm->mmap_control == MAP_FAILED)
+ pcm->mmap_control = NULL;
+ if (!pcm->mmap_control) {
+ pcm->ops->munmap(pcm->data, pcm->mmap_status, page_size);
+ pcm->mmap_status = NULL;
+ goto mmap_error;
+ }
+
+ return 0;
+
+mmap_error:
+
+ pcm->sync_ptr = calloc(1, sizeof(*pcm->sync_ptr));
+ if (!pcm->sync_ptr)
+ return -ENOMEM;
+ pcm->mmap_status = &pcm->sync_ptr->s.status;
+ pcm->mmap_control = &pcm->sync_ptr->c.control;
+
+ return 0;
+}
+
+static void pcm_hw_munmap_status(struct pcm *pcm) {
+ if (pcm->sync_ptr) {
+ free(pcm->sync_ptr);
+ pcm->sync_ptr = NULL;
+ } else {
+ int page_size = sysconf(_SC_PAGE_SIZE);
+ if (pcm->mmap_status)
+ pcm->ops->munmap(pcm->data, pcm->mmap_status, page_size);
+ if (pcm->mmap_control)
+ pcm->ops->munmap(pcm->data, pcm->mmap_control, page_size);
+ }
+ pcm->mmap_status = NULL;
+ pcm->mmap_control = NULL;
+}
+
+static struct pcm bad_pcm = {
+ .fd = -1,
+};
+
+/** Gets the hardware parameters of a PCM, without created a PCM handle.
+ * @param card The card of the PCM.
+ * The default card is zero.
+ * @param device The device of the PCM.
+ * The default device is zero.
+ * @param flags Specifies whether the PCM is an input or output.
+ * May be one of the following:
+ * - @ref PCM_IN
+ * - @ref PCM_OUT
+ * @return On success, the hardware parameters of the PCM; on failure, NULL.
+ * @ingroup libtinyalsa-pcm
+ */
+struct pcm_params *pcm_params_get(unsigned int card, unsigned int device,
+ unsigned int flags)
+{
+ struct snd_pcm_hw_params *params;
+ void *snd_node = NULL, *data;
+ const struct pcm_ops *ops;
+ int fd;
+
+ ops = &hw_ops;
+ fd = ops->open(card, device, flags, &data, snd_node);
+
+#ifdef TINYALSA_USES_PLUGINS
+ if (fd < 0) {
+ int pcm_type;
+ snd_node = snd_utils_open_pcm(card, device);
+ pcm_type = snd_utils_get_node_type(snd_node);
+ if (!snd_node || pcm_type != SND_NODE_TYPE_PLUGIN) {
+ fprintf(stderr, "no device (hw/plugin) for card(%u), device(%u)",
+ card, device);
+ goto err_open;
+ }
+ ops = &plug_ops;
+ fd = ops->open(card, device, flags, &data, snd_node);
+ }
+#endif
+ if (fd < 0) {
+ fprintf(stderr, "cannot open card(%d) device (%d): %s\n",
+ card, device, strerror(errno));
+ goto err_open;
+ }
+
+ params = calloc(1, sizeof(struct snd_pcm_hw_params));
+ if (!params)
+ goto err_calloc;
+
+ param_init(params);
+ if (ops->ioctl(data, SNDRV_PCM_IOCTL_HW_REFINE, params)) {
+ fprintf(stderr, "SNDRV_PCM_IOCTL_HW_REFINE error (%d)\n", errno);
+ goto err_hw_refine;
+ }
+
+#ifdef TINYALSA_USES_PLUGINS
+ if (snd_node)
+ snd_utils_close_dev_node(snd_node);
+#endif
+ ops->close(data);
+
+ return (struct pcm_params *)params;
+
+err_hw_refine:
+ free(params);
+err_calloc:
+#ifdef TINYALSA_USES_PLUGINS
+ if (snd_node)
+ snd_utils_close_dev_node(snd_node);
+#endif
+ ops->close(data);
+err_open:
+ return NULL;
+}
+
+/** Frees the hardware parameters returned by @ref pcm_params_get.
+ * @param pcm_params Hardware parameters of a PCM.
+ * May be NULL.
+ * @ingroup libtinyalsa-pcm
+ */
+void pcm_params_free(struct pcm_params *pcm_params)
+{
+ struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
+
+ if (params)
+ free(params);
+}
+
+static int pcm_param_to_alsa(enum pcm_param param)
+{
+ switch (param) {
+ case PCM_PARAM_ACCESS:
+ return SNDRV_PCM_HW_PARAM_ACCESS;
+ case PCM_PARAM_FORMAT:
+ return SNDRV_PCM_HW_PARAM_FORMAT;
+ case PCM_PARAM_SUBFORMAT:
+ return SNDRV_PCM_HW_PARAM_SUBFORMAT;
+ case PCM_PARAM_SAMPLE_BITS:
+ return SNDRV_PCM_HW_PARAM_SAMPLE_BITS;
+ break;
+ case PCM_PARAM_FRAME_BITS:
+ return SNDRV_PCM_HW_PARAM_FRAME_BITS;
+ break;
+ case PCM_PARAM_CHANNELS:
+ return SNDRV_PCM_HW_PARAM_CHANNELS;
+ break;
+ case PCM_PARAM_RATE:
+ return SNDRV_PCM_HW_PARAM_RATE;
+ break;
+ case PCM_PARAM_PERIOD_TIME:
+ return SNDRV_PCM_HW_PARAM_PERIOD_TIME;
+ break;
+ case PCM_PARAM_PERIOD_SIZE:
+ return SNDRV_PCM_HW_PARAM_PERIOD_SIZE;
+ break;
+ case PCM_PARAM_PERIOD_BYTES:
+ return SNDRV_PCM_HW_PARAM_PERIOD_BYTES;
+ break;
+ case PCM_PARAM_PERIODS:
+ return SNDRV_PCM_HW_PARAM_PERIODS;
+ break;
+ case PCM_PARAM_BUFFER_TIME:
+ return SNDRV_PCM_HW_PARAM_BUFFER_TIME;
+ break;
+ case PCM_PARAM_BUFFER_SIZE:
+ return SNDRV_PCM_HW_PARAM_BUFFER_SIZE;
+ break;
+ case PCM_PARAM_BUFFER_BYTES:
+ return SNDRV_PCM_HW_PARAM_BUFFER_BYTES;
+ break;
+ case PCM_PARAM_TICK_TIME:
+ return SNDRV_PCM_HW_PARAM_TICK_TIME;
+ break;
+
+ default:
+ return -1;
+ }
+}
+
+/** Gets a mask from a PCM's hardware parameters.
+ * @param pcm_params A PCM's hardware parameters.
+ * @param param The parameter to get.
+ * @return If @p pcm_params is NULL or @p param is not a mask, NULL is returned.
+ * Otherwise, the mask associated with @p param is returned.
+ * @ingroup libtinyalsa-pcm
+ */
+const struct pcm_mask *pcm_params_get_mask(const struct pcm_params *pcm_params,
+ enum pcm_param param)
+{
+ int p;
+ struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
+ if (params == NULL) {
+ return NULL;
+ }
+
+ p = pcm_param_to_alsa(param);
+ if (p < 0 || !param_is_mask(p)) {
+ return NULL;
+ }
+
+ return (const struct pcm_mask *)param_to_mask(params, p);
+}
+
+/** Get the minimum of a specified PCM parameter.
+ * @param pcm_params A PCM parameters structure.
+ * @param param The specified parameter to get the minimum of.
+ * @returns On success, the parameter minimum.
+ * On failure, zero.
+ */
+unsigned int pcm_params_get_min(const struct pcm_params *pcm_params,
+ enum pcm_param param)
+{
+ struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
+ int p;
+
+ if (!params)
+ return 0;
+
+ p = pcm_param_to_alsa(param);
+ if (p < 0)
+ return 0;
+
+ return param_get_min(params, p);
+}
+
+/** Get the maximum of a specified PCM parameter.
+ * @param pcm_params A PCM parameters structure.
+ * @param param The specified parameter to get the maximum of.
+ * @returns On success, the parameter maximum.
+ * On failure, zero.
+ */
+unsigned int pcm_params_get_max(const struct pcm_params *pcm_params,
+ enum pcm_param param)
+{
+ const struct snd_pcm_hw_params *params = (const struct snd_pcm_hw_params *)pcm_params;
+ int p;
+
+ if (!params)
+ return 0;
+
+ p = pcm_param_to_alsa(param);
+ if (p < 0)
+ return 0;
+
+ return param_get_max(params, p);
+}
+
+static int pcm_mask_test(const struct pcm_mask *m, unsigned int index)
+{
+ const unsigned int bitshift = 5; /* for 32 bit integer */
+ const unsigned int bitmask = (1 << bitshift) - 1;
+ unsigned int element;
+
+ element = index >> bitshift;
+ if (element >= ARRAY_SIZE(m->bits))
+ return 0; /* for safety, but should never occur */
+ return (m->bits[element] >> (index & bitmask)) & 1;
+}
+
+static int pcm_mask_to_string(const struct pcm_mask *m, char *string, unsigned int size,
+ char *mask_name,
+ const char * const *bit_array_name, size_t bit_array_size)
+{
+ unsigned int i;
+ unsigned int offset = 0;
+
+ if (m == NULL)
+ return 0;
+ if (bit_array_size < 32) {
+ STRLOG(string, offset, size, "%12s:\t%#08x\n", mask_name, m->bits[0]);
+ } else { /* spans two or more bitfields, print with an array index */
+ for (i = 0; i < (bit_array_size + 31) >> 5; ++i) {
+ STRLOG(string, offset, size, "%9s[%d]:\t%#08x\n",
+ mask_name, i, m->bits[i]);
+ }
+ }
+ for (i = 0; i < bit_array_size; ++i) {
+ if (pcm_mask_test(m, i)) {
+ STRLOG(string, offset, size, "%12s \t%s\n", "", bit_array_name[i]);
+ }
+ }
+ return offset;
+}
+
+int pcm_params_to_string(struct pcm_params *params, char *string, unsigned int size)
+{
+ const struct pcm_mask *m;
+ unsigned int min, max;
+ unsigned int clipoffset, offset;
+
+ m = pcm_params_get_mask(params, PCM_PARAM_ACCESS);
+ offset = pcm_mask_to_string(m, string, size,
+ "Access", access_lookup, ARRAY_SIZE(access_lookup));
+ m = pcm_params_get_mask(params, PCM_PARAM_FORMAT);
+ clipoffset = offset > size ? size : offset;
+ offset += pcm_mask_to_string(m, string + clipoffset, size - clipoffset,
+ "Format", format_lookup, ARRAY_SIZE(format_lookup));
+ m = pcm_params_get_mask(params, PCM_PARAM_SUBFORMAT);
+ clipoffset = offset > size ? size : offset;
+ offset += pcm_mask_to_string(m, string + clipoffset, size - clipoffset,
+ "Subformat", subformat_lookup, ARRAY_SIZE(subformat_lookup));
+ min = pcm_params_get_min(params, PCM_PARAM_RATE);
+ max = pcm_params_get_max(params, PCM_PARAM_RATE);
+ STRLOG(string, offset, size, " Rate:\tmin=%uHz\tmax=%uHz\n", min, max);
+ min = pcm_params_get_min(params, PCM_PARAM_CHANNELS);
+ max = pcm_params_get_max(params, PCM_PARAM_CHANNELS);
+ STRLOG(string, offset, size, " Channels:\tmin=%u\t\tmax=%u\n", min, max);
+ min = pcm_params_get_min(params, PCM_PARAM_SAMPLE_BITS);
+ max = pcm_params_get_max(params, PCM_PARAM_SAMPLE_BITS);
+ STRLOG(string, offset, size, " Sample bits:\tmin=%u\t\tmax=%u\n", min, max);
+ min = pcm_params_get_min(params, PCM_PARAM_PERIOD_SIZE);
+ max = pcm_params_get_max(params, PCM_PARAM_PERIOD_SIZE);
+ STRLOG(string, offset, size, " Period size:\tmin=%u\t\tmax=%u\n", min, max);
+ min = pcm_params_get_min(params, PCM_PARAM_PERIODS);
+ max = pcm_params_get_max(params, PCM_PARAM_PERIODS);
+ STRLOG(string, offset, size, "Period count:\tmin=%u\t\tmax=%u\n", min, max);
+ return offset;
+}
+
+int pcm_params_format_test(struct pcm_params *params, enum pcm_format format)
+{
+ unsigned int alsa_format = pcm_format_to_alsa(format);
+
+ if (alsa_format == SNDRV_PCM_FORMAT_S16_LE && format != PCM_FORMAT_S16_LE)
+ return 0; /* caution: format not recognized is equivalent to S16_LE */
+ return pcm_mask_test(pcm_params_get_mask(params, PCM_PARAM_FORMAT), alsa_format);
+}
+
+/** Closes a PCM returned by @ref pcm_open.
+ * @param pcm A PCM returned by @ref pcm_open.
+ * May not be NULL.
+ * @return Always returns zero.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_close(struct pcm *pcm)
+{
+ if (pcm == &bad_pcm)
+ return 0;
+
+ pcm_hw_munmap_status(pcm);
+
+ if (pcm->flags & PCM_MMAP) {
+ pcm_stop(pcm);
+ pcm->ops->munmap(pcm->data, pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
+ }
+
+ snd_utils_close_dev_node(pcm->snd_node);
+ pcm->ops->close(pcm->data);
+ pcm->buffer_size = 0;
+ pcm->fd = -1;
+ free(pcm);
+ return 0;
+}
+
+/** Opens a PCM by it's name.
+ * @param name The name of the PCM.
+ * The name is given in the format: <i>hw</i>:<b>card</b>,<b>device</b>
+ * @param flags Specify characteristics and functionality about the pcm.
+ * May be a bitwise AND of the following:
+ * - @ref PCM_IN
+ * - @ref PCM_OUT
+ * - @ref PCM_MMAP
+ * - @ref PCM_NOIRQ
+ * - @ref PCM_MONOTONIC
+ * @param config The hardware and software parameters to open the PCM with.
+ * @returns A PCM structure.
+ * If an error occurs allocating memory for the PCM, NULL is returned.
+ * Otherwise, client code should check that the PCM opened properly by calling @ref pcm_is_ready.
+ * If @ref pcm_is_ready, check @ref pcm_get_error for more information.
+ * @ingroup libtinyalsa-pcm
+ */
+struct pcm *pcm_open_by_name(const char *name,
+ unsigned int flags,
+ const struct pcm_config *config)
+{
+ unsigned int card, device;
+ if ((name[0] != 'h')
+ || (name[1] != 'w')
+ || (name[2] != ':')) {
+ return NULL;
+ } else if (sscanf(&name[3], "%u,%u", &card, &device) != 2) {
+ return NULL;
+ }
+ return pcm_open(card, device, flags, config);
+}
+
+/** Opens a PCM.
+ * @param card The card that the pcm belongs to.
+ * The default card is zero.
+ * @param device The device that the pcm belongs to.
+ * The default device is zero.
+ * @param flags Specify characteristics and functionality about the pcm.
+ * May be a bitwise AND of the following:
+ * - @ref PCM_IN
+ * - @ref PCM_OUT
+ * - @ref PCM_MMAP
+ * - @ref PCM_NOIRQ
+ * - @ref PCM_MONOTONIC
+ * @param config The hardware and software parameters to open the PCM with.
+ * @returns A PCM structure.
+ * If an error occurs allocating memory for the PCM, NULL is returned.
+ * Otherwise, client code should check that the PCM opened properly by calling @ref pcm_is_ready.
+ * If @ref pcm_is_ready, check @ref pcm_get_error for more information.
+ * @ingroup libtinyalsa-pcm
+ */
+struct pcm *pcm_open(unsigned int card, unsigned int device,
+ unsigned int flags, const struct pcm_config *config)
+{
+ struct pcm *pcm;
+ struct snd_pcm_info info;
+ int rc;
+
+ pcm = calloc(1, sizeof(struct pcm));
+ if (!pcm)
+ return &bad_pcm;
+
+ /* Default to hw_ops, attemp plugin open only if hw (/dev/snd/pcm*) open fails */
+ pcm->ops = &hw_ops;
+ pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, NULL);
+
+#ifdef TINYALSA_USES_PLUGINS
+ if (pcm->fd < 0) {
+ int pcm_type;
+ pcm->snd_node = snd_utils_open_pcm(card, device);
+ pcm_type = snd_utils_get_node_type(pcm->snd_node);
+ if (!pcm->snd_node || pcm_type != SND_NODE_TYPE_PLUGIN) {
+ oops(pcm, -ENODEV, "no device (hw/plugin) for card(%u), device(%u)",
+ card, device);
+ goto fail_close_dev_node;
+ }
+ pcm->ops = &plug_ops;
+ pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, pcm->snd_node);
+ }
+#endif
+ if (pcm->fd < 0) {
+ oops(pcm, errno, "cannot open device (%u) for card (%u)",
+ device, card);
+ goto fail_close_dev_node;
+ }
+
+ pcm->flags = flags;
+
+ if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_INFO, &info)) {
+ oops(pcm, errno, "cannot get info");
+ goto fail_close;
+ }
+ pcm->subdevice = info.subdevice;
+
+ if (pcm_set_config(pcm, config) != 0)
+ goto fail_close;
+
+ rc = pcm_hw_mmap_status(pcm);
+ if (rc < 0) {
+ oops(pcm, errno, "mmap status failed");
+ goto fail;
+ }
+
+#ifdef SNDRV_PCM_IOCTL_TTSTAMP
+ if (pcm->flags & PCM_MONOTONIC) {
+ int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;
+ rc = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_TTSTAMP, &arg);
+ if (rc < 0) {
+ oops(pcm, errno, "cannot set timestamp type");
+ goto fail;
+ }
+ }
+#endif
+
+ /* prepare here so the user does not need to do this later */
+ if (pcm_prepare(pcm))
+ goto fail;
+
+ pcm->xruns = 0;
+ return pcm;
+
+fail:
+ pcm_hw_munmap_status(pcm);
+ if (flags & PCM_MMAP)
+ pcm->ops->munmap(pcm->data, pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
+fail_close:
+ pcm->ops->close(pcm->data);
+fail_close_dev_node:
+#ifdef TINYALSA_USES_PLUGINS
+ if (pcm->snd_node)
+ snd_utils_close_dev_node(pcm->snd_node);
+#endif
+ free(pcm);
+ return &bad_pcm;
+}
+
+/** Checks if a PCM file has been opened without error.
+ * @param pcm A PCM handle.
+ * May be NULL.
+ * @return If a PCM's file descriptor is not valid or the pointer is NULL, it returns zero.
+ * Otherwise, the function returns one.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_is_ready(const struct pcm *pcm)
+{
+ if (pcm != NULL) {
+ return pcm->fd >= 0;
+ }
+ return 0;
+}
+
+/** Links two PCMs.
+ * After this function is called, the two PCMs will prepare, start and stop in sync (at the same time).
+ * If an error occurs, the error message will be written to @p pcm1.
+ * @param pcm1 A PCM handle.
+ * @param pcm2 Another PCM handle.
+ * @return On success, zero; on failure, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_link(struct pcm *pcm1, struct pcm *pcm2)
+{
+ int err = ioctl(pcm1->fd, SNDRV_PCM_IOCTL_LINK, pcm2->fd);
+ if (err == -1) {
+ return oops(pcm1, errno, "cannot link PCM");
+ }
+ return 0;
+}
+
+/** Unlinks a PCM.
+ * @see @ref pcm_link
+ * @param pcm A PCM handle.
+ * @return On success, zero; on failure, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_unlink(struct pcm *pcm)
+{
+ int err = ioctl(pcm->fd, SNDRV_PCM_IOCTL_UNLINK);
+ if (err == -1) {
+ return oops(pcm, errno, "cannot unlink PCM");
+ }
+ return 0;
+}
+
+/** Prepares a PCM, if it has not been prepared already.
+ * @param pcm A PCM handle.
+ * @return On success, zero; on failure, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_prepare(struct pcm *pcm)
+{
+ if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_PREPARE) < 0)
+ return oops(pcm, errno, "cannot prepare channel");
+
+ /* get appl_ptr and avail_min from kernel */
+ pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL|SNDRV_PCM_SYNC_PTR_AVAIL_MIN);
+
+ return 0;
+}
+
+/** Starts a PCM.
+ * @param pcm A PCM handle.
+ * @return On success, zero; on failure, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_start(struct pcm *pcm)
+{
+ /* set appl_ptr and avail_min in kernel */
+ if (pcm_sync_ptr(pcm, 0) < 0)
+ return -1;
+
+ if (pcm->mmap_status->state != PCM_STATE_RUNNING) {
+ if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_START) < 0)
+ return oops(pcm, errno, "cannot start channel");
+ }
+
+ return 0;
+}
+
+/** Stops a PCM.
+ * @param pcm A PCM handle.
+ * @return On success, zero; on failure, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_stop(struct pcm *pcm)
+{
+ if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_DROP) < 0)
+ return oops(pcm, errno, "cannot stop channel");
+
+ return 0;
+}
+
+static inline int pcm_mmap_playback_avail(struct pcm *pcm)
+{
+ int avail;
+
+ avail = pcm->mmap_status->hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr;
+
+ if (avail < 0)
+ avail += pcm->boundary;
+ else if (avail >= (int)pcm->boundary)
+ avail -= pcm->boundary;
+
+ return avail;
+}
+
+static inline int pcm_mmap_capture_avail(struct pcm *pcm)
+{
+ int avail = pcm->mmap_status->hw_ptr - pcm->mmap_control->appl_ptr;
+ if (avail < 0)
+ avail += pcm->boundary;
+ return avail;
+}
+
+int pcm_mmap_avail(struct pcm *pcm)
+{
+ if (pcm->flags & PCM_IN)
+ return pcm_mmap_capture_avail(pcm);
+ else
+ return pcm_mmap_playback_avail(pcm);
+}
+
+static void pcm_mmap_appl_forward(struct pcm *pcm, int frames)
+{
+ unsigned int appl_ptr = pcm->mmap_control->appl_ptr;
+ appl_ptr += frames;
+
+ /* check for boundary wrap */
+ if (appl_ptr > pcm->boundary)
+ appl_ptr -= pcm->boundary;
+ pcm->mmap_control->appl_ptr = appl_ptr;
+}
+
+int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset,
+ unsigned int *frames)
+{
+ unsigned int continuous, copy_frames, avail;
+
+ /* return the mmap buffer */
+ *areas = pcm->mmap_buffer;
+
+ /* and the application offset in frames */
+ *offset = pcm->mmap_control->appl_ptr % pcm->buffer_size;
+
+ avail = pcm_mmap_avail(pcm);
+ if (avail > pcm->buffer_size)
+ avail = pcm->buffer_size;
+ continuous = pcm->buffer_size - *offset;
+
+ /* we can only copy frames if the are availabale and continuos */
+ copy_frames = *frames;
+ if (copy_frames > avail)
+ copy_frames = avail;
+ if (copy_frames > continuous)
+ copy_frames = continuous;
+ *frames = copy_frames;
+
+ return 0;
+}
+
+static int pcm_areas_copy(struct pcm *pcm, unsigned int pcm_offset,
+ char *buf, unsigned int src_offset,
+ unsigned int frames)
+{
+ int size_bytes = pcm_frames_to_bytes(pcm, frames);
+ int pcm_offset_bytes = pcm_frames_to_bytes(pcm, pcm_offset);
+ int src_offset_bytes = pcm_frames_to_bytes(pcm, src_offset);
+
+ /* interleaved only atm */
+ if (pcm->flags & PCM_IN)
+ memcpy(buf + src_offset_bytes,
+ (char*)pcm->mmap_buffer + pcm_offset_bytes,
+ size_bytes);
+ else
+ memcpy((char*)pcm->mmap_buffer + pcm_offset_bytes,
+ buf + src_offset_bytes,
+ size_bytes);
+ return 0;
+}
+
+int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames)
+{
+ int ret;
+
+ /* not used */
+ (void) offset;
+
+ /* update the application pointer in userspace and kernel */
+ pcm_mmap_appl_forward(pcm, frames);
+ ret = pcm_sync_ptr(pcm, 0);
+ if (ret != 0){
+ printf("%d\n", ret);
+ return ret;
+ }
+
+ return frames;
+}
+
+static int pcm_mmap_transfer_areas(struct pcm *pcm, char *buf,
+ unsigned int offset, unsigned int size)
+{
+ void *pcm_areas;
+ int commit;
+ unsigned int pcm_offset, frames, count = 0;
+
+ while (pcm_mmap_avail(pcm) && size) {
+ frames = size;
+ pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames);
+ pcm_areas_copy(pcm, pcm_offset, buf, offset, frames);
+ commit = pcm_mmap_commit(pcm, pcm_offset, frames);
+ if (commit < 0) {
+ oops(pcm, commit, "failed to commit %d frames\n", frames);
+ return commit;
+ }
+
+ offset += commit;
+ count += commit;
+ size -= commit;
+ }
+ return count;
+}
+
+int pcm_get_poll_fd(struct pcm *pcm)
+{
+ return pcm->fd;
+}
+
+int pcm_avail_update(struct pcm *pcm)
+{
+ pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL|SNDRV_PCM_SYNC_PTR_AVAIL_MIN);
+ return pcm_mmap_avail(pcm);
+}
+
+/** Returns available frames in pcm buffer and corresponding time stamp.
+ * The clock is CLOCK_MONOTONIC if flag @ref PCM_MONOTONIC was specified in @ref pcm_open,
+ * otherwise the clock is CLOCK_REALTIME.
+ * For an input stream, frames available are frames ready for the application to read.
+ * For an output stream, frames available are the number of empty frames available for the application to write.
+ * @param pcm A PCM handle.
+ * @param avail The number of available frames
+ * @param tstamp The timestamp
+ * @return On success, zero is returned; on failure, negative one.
+ */
+int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail,
+ struct timespec *tstamp)
+{
+ int checking;
+ int tmp;
+
+ if (!pcm_is_ready(pcm))
+ return -1;
+
+ checking = 0;
+
+again:
+
+ tmp = pcm_avail_update(pcm);
+ if (tmp < 0)
+ return tmp; /* error */
+
+ if (checking && (unsigned int) tmp == *avail)
+ return 0;
+
+ *avail = (unsigned int) tmp;
+ *tstamp = pcm->mmap_status->tstamp;
+
+ /*
+ * When status is mmaped, get avail again to ensure
+ * valid timestamp.
+ */
+ if (!pcm->sync_ptr) {
+ checking = 1;
+ goto again;
+ }
+
+ /* SYNC_PTR ioctl was used, no need to check avail */
+ return 0;
+}
+
+int pcm_state(struct pcm *pcm)
+{
+ int err = pcm_sync_ptr(pcm, 0);
+ if (err < 0)
+ return err;
+
+ return pcm->mmap_status->state;
+}
+
+/** Waits for frames to be available for read or write operations.
+ * @param pcm A PCM handle.
+ * @param timeout The maximum amount of time to wait for, in terms of milliseconds.
+ * @returns If frames became available, one is returned.
+ * If a timeout occured, zero is returned.
+ * If an error occured, a negative number is returned.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_wait(struct pcm *pcm, int timeout)
+{
+ struct pollfd pfd;
+ int err;
+
+ pfd.fd = pcm->fd;
+ pfd.events = POLLIN | POLLOUT | POLLERR | POLLNVAL;
+
+ do {
+ /* let's wait for avail or timeout */
+ err = pcm->ops->poll(pcm->data, &pfd, 1, timeout);
+ if (err < 0)
+ return -errno;
+
+ /* timeout ? */
+ if (err == 0)
+ return 0;
+
+ /* have we been interrupted ? */
+ if (errno == -EINTR)
+ continue;
+
+ /* check for any errors */
+ if (pfd.revents & (POLLERR | POLLNVAL)) {
+ switch (pcm_state(pcm)) {
+ case PCM_STATE_XRUN:
+ return -EPIPE;
+ case PCM_STATE_SUSPENDED:
+ return -ESTRPIPE;
+ case PCM_STATE_DISCONNECTED:
+ return -ENODEV;
+ default:
+ return -EIO;
+ }
+ }
+ /* poll again if fd not ready for IO */
+ } while (!(pfd.revents & (POLLIN | POLLOUT)));
+
+ return 1;
+}
+
+/*
+ * Transfer data to/from mmaped buffer. This imitates the
+ * behavior of read/write system calls.
+ *
+ * However, this doesn't seems to offer any advantage over
+ * the read/write syscalls. Should it be removed?
+ */
+int pcm_mmap_transfer(struct pcm *pcm, void *buffer, unsigned int frames)
+{
+ int is_playback;
+
+ int state;
+ unsigned int avail;
+ unsigned int user_offset;
+
+ int err;
+ int tmp;
+
+ is_playback = !(pcm->flags & PCM_IN);
+
+ if (frames == 0)
+ return 0;
+
+ /* update hardware pointer and get state */
+ err = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC |
+ SNDRV_PCM_SYNC_PTR_APPL |
+ SNDRV_PCM_SYNC_PTR_AVAIL_MIN);
+ if (err == -1)
+ return -1;
+ state = pcm->mmap_status->state;
+
+ /*
+ * If frames < start_threshold, wait indefinitely.
+ * Another thread may start capture
+ */
+ if (!is_playback && state == PCM_STATE_PREPARED &&
+ frames >= pcm->config.start_threshold) {
+ err = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_START);
+ if (err == -1)
+ return -1;
+ /* state = PCM_STATE_RUNNING */
+ }
+
+ avail = pcm_mmap_avail(pcm);
+ user_offset = 0;
+
+ while (frames) {
+ if (!avail) {
+ if (pcm->flags & PCM_NONBLOCK) {
+ errno = EAGAIN;
+ break;
+ }
+
+ /* wait for interrupt */
+ err = pcm_wait(pcm, -1);
+ if (err < 0) {
+ errno = -err;
+ break;
+ }
+
+ /* get hardware pointer */
+ avail = pcm_avail_update(pcm);
+ }
+
+ tmp = pcm_mmap_transfer_areas(pcm, buffer, user_offset, frames);
+ if (tmp < 0)
+ break;
+
+ user_offset += tmp;
+ frames -= tmp;
+ avail -= tmp;
+
+ /* start playback if written >= start_threshold */
+ if (is_playback && state == PCM_STATE_PREPARED &&
+ pcm->buffer_size - avail >= pcm->config.start_threshold) {
+ err = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_START);
+ if (err == -1)
+ break;
+ }
+ }
+
+ return user_offset ? (int) user_offset : -1;
+}
+
+int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count)
+{
+ if ((~pcm->flags) & (PCM_OUT | PCM_MMAP))
+ return -ENOSYS;
+
+ return pcm_mmap_transfer(pcm, (void *)data,
+ pcm_bytes_to_frames(pcm, count));
+}
+
+int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count)
+{
+ if ((~pcm->flags) & (PCM_IN | PCM_MMAP))
+ return -ENOSYS;
+
+ return pcm_mmap_transfer(pcm, data, pcm_bytes_to_frames(pcm, count));
+}
+
+/* Returns current read/write position in the mmap buffer with associated time stamp. */
+int pcm_mmap_get_hw_ptr(struct pcm* pcm, unsigned int *hw_ptr, struct timespec *tstamp)
+{
+ int rc;
+
+ if (pcm == NULL || hw_ptr == NULL || tstamp == NULL)
+ return oops(pcm, EINVAL, "pcm %p, hw_ptr %p, tstamp %p", pcm, hw_ptr, tstamp);
+
+ if (!pcm_is_ready(pcm))
+ return oops(pcm, errno, "pcm_is_ready failed");
+
+ rc = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC);
+ if (rc < 0)
+ return oops(pcm, errno, "pcm_sync_ptr failed");
+
+ if (pcm->mmap_status == NULL)
+ return oops(pcm, EINVAL, "pcm %p, mmap_status is NULL", pcm);
+
+ if ((pcm->mmap_status->state != PCM_STATE_RUNNING) &&
+ (pcm->mmap_status->state != PCM_STATE_DRAINING))
+ return oops(pcm, ENOSYS, "invalid stream state %d", pcm->mmap_status->state);
+
+ *tstamp = pcm->mmap_status->tstamp;
+ if (tstamp->tv_sec == 0 && tstamp->tv_nsec == 0)
+ return oops(pcm, errno, "invalid time stamp");
+
+ *hw_ptr = pcm->mmap_status->hw_ptr;
+
+ return 0;
+}
+
+static int pcm_rw_transfer(struct pcm *pcm, void *data, unsigned int frames)
+{
+ int is_playback;
+
+ struct snd_xferi transfer;
+ int res;
+
+ is_playback = !(pcm->flags & PCM_IN);
+
+ transfer.buf = data;
+ transfer.frames = frames;
+ transfer.result = 0;
+
+ res = pcm->ops->ioctl(pcm->data, is_playback
+ ? SNDRV_PCM_IOCTL_WRITEI_FRAMES
+ : SNDRV_PCM_IOCTL_READI_FRAMES, &transfer);
+
+ return res == 0 ? (int) transfer.result : -1;
+}
+
+static int pcm_generic_transfer(struct pcm *pcm, void *data,
+ unsigned int frames)
+{
+ int res;
+
+#if UINT_MAX > TINYALSA_FRAMES_MAX
+ if (frames > TINYALSA_FRAMES_MAX)
+ return -EINVAL;
+#endif
+ if (frames > INT_MAX)
+ return -EINVAL;
+
+again:
+
+ if (pcm->flags & PCM_MMAP)
+ res = pcm_mmap_transfer(pcm, data, frames);
+ else
+ res = pcm_rw_transfer(pcm, data, frames);
+
+ if (res < 0) {
+ switch (errno) {
+ case EPIPE:
+ pcm->xruns++;
+ /* fallthrough */
+ case ESTRPIPE:
+ /*
+ * Try to restart if we are allowed to do so.
+ * Otherwise, return error.
+ */
+ if (pcm->flags & PCM_NORESTART || pcm_prepare(pcm))
+ return -1;
+ goto again;
+ case EAGAIN:
+ if (pcm->flags & PCM_NONBLOCK)
+ return -1;
+ /* fallthrough */
+ default:
+ return oops(pcm, errno, "cannot read/write stream data");
+ }
+ }
+
+ return res;
+}
+
+/** Writes audio samples to PCM.
+ * If the PCM has not been started, it is started in this function.
+ * This function is only valid for PCMs opened with the @ref PCM_OUT flag.
+ * @param pcm A PCM handle.
+ * @param data The audio sample array
+ * @param frame_count The number of frames occupied by the sample array.
+ * This value should not be greater than @ref TINYALSA_FRAMES_MAX
+ * or INT_MAX.
+ * @return On success, this function returns the number of frames written; otherwise, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_writei(struct pcm *pcm, const void *data, unsigned int frame_count)
+{
+ if (pcm->flags & PCM_IN)
+ return -EINVAL;
+
+ return pcm_generic_transfer(pcm, (void*) data, frame_count);
+}
+
+/** Reads audio samples from PCM.
+ * If the PCM has not been started, it is started in this function.
+ * This function is only valid for PCMs opened with the @ref PCM_IN flag.
+ * @param pcm A PCM handle.
+ * @param data The audio sample array
+ * @param frame_count The number of frames occupied by the sample array.
+ * This value should not be greater than @ref TINYALSA_FRAMES_MAX
+ * or INT_MAX.
+ * @return On success, this function returns the number of frames written; otherwise, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_readi(struct pcm *pcm, void *data, unsigned int frame_count)
+{
+ if (!(pcm->flags & PCM_IN))
+ return -EINVAL;
+
+ return pcm_generic_transfer(pcm, data, frame_count);
+}
+
+/** Writes audio samples to PCM.
+ * If the PCM has not been started, it is started in this function.
+ * This function is only valid for PCMs opened with the @ref PCM_OUT flag.
+ * This function is not valid for PCMs opened with the @ref PCM_MMAP flag.
+ * @param pcm A PCM handle.
+ * @param data The audio sample array
+ * @param count The number of bytes occupied by the sample array.
+ * @return On success, this function returns zero; otherwise, a negative number.
+ * @deprecated
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
+{
+ unsigned int requested_frames = pcm_bytes_to_frames(pcm, count);
+ int ret = pcm_writei(pcm, data, requested_frames);
+
+ if (ret < 0)
+ return ret;
+
+ return ((unsigned int )ret == requested_frames) ? 0 : -EIO;
+}
+
+/** Reads audio samples from PCM.
+ * If the PCM has not been started, it is started in this function.
+ * This function is only valid for PCMs opened with the @ref PCM_IN flag.
+ * This function is not valid for PCMs opened with the @ref PCM_MMAP flag.
+ * @param pcm A PCM handle.
+ * @param data The audio sample array
+ * @param count The number of bytes occupied by the sample array.
+ * @return On success, this function returns zero; otherwise, a negative number.
+ * @deprecated
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_read(struct pcm *pcm, void *data, unsigned int count)
+{
+ unsigned int requested_frames = pcm_bytes_to_frames(pcm, count);
+ int ret = pcm_readi(pcm, data, requested_frames);
+
+ if (ret < 0)
+ return ret;
+
+ return ((unsigned int )ret == requested_frames) ? 0 : -EIO;
+}
+
+/** Gets the delay of the PCM, in terms of frames.
+ * @param pcm A PCM handle.
+ * @returns On success, the delay of the PCM.
+ * On failure, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
+long pcm_get_delay(struct pcm *pcm)
+{
+ if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DELAY, &pcm->pcm_delay) < 0)
+ return -1;
+
+ return pcm->pcm_delay;
+}
+
+// TODO: Currently in Android, there are some libraries using this function to control the driver.
+// We should remove this function as soon as possible.
+int pcm_ioctl(struct pcm *pcm, int request, ...)
+{
+ va_list ap;
+ void * arg;
+
+ if (!pcm_is_ready(pcm))
+ return -1;
+
+ va_start(ap, request);
+ arg = va_arg(ap, void *);
+ va_end(ap);
+
+ // FIXME Does not handle plugins
+ return ioctl(pcm->fd, request, arg);
+}
diff --git a/src/pcm_hw.c b/src/pcm_hw.c
new file mode 100644
index 0000000..38b2e83
--- /dev/null
+++ b/src/pcm_hw.c
@@ -0,0 +1,142 @@
+/* pcm_hw.c
+** Copyright (c) 2019, The Linux Foundation.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <poll.h>
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <linux/ioctl.h>
+#include <sound/asound.h>
+#include <tinyalsa/asoundlib.h>
+
+#include "pcm_io.h"
+
+struct pcm_hw_data {
+ /** Card number of the pcm device */
+ unsigned int card;
+ /** Device number for the pcm device */
+ unsigned int device;
+ /** File descriptor to the pcm device file node */
+ unsigned int fd;
+ /** Pointer to the pcm node from snd card definiton */
+ struct snd_node *node;
+};
+
+static void pcm_hw_close(void *data)
+{
+ struct pcm_hw_data *hw_data = data;
+
+ if (hw_data->fd > 0)
+ close(hw_data->fd);
+
+ free(hw_data);
+}
+
+static int pcm_hw_ioctl(void *data, unsigned int cmd, ...)
+{
+ struct pcm_hw_data *hw_data = data;
+ va_list ap;
+ void *arg;
+
+ va_start(ap, cmd);
+ arg = va_arg(ap, void *);
+ va_end(ap);
+
+ return ioctl(hw_data->fd, cmd, arg);
+}
+
+static int pcm_hw_poll(void *data __attribute__((unused)),
+ struct pollfd *pfd, nfds_t nfds, int timeout)
+{
+ return poll(pfd, nfds, timeout);
+}
+
+static void *pcm_hw_mmap(void *data, void *addr, size_t length, int prot,
+ int flags, off_t offset)
+{
+ struct pcm_hw_data *hw_data = data;
+
+ return mmap(addr, length, prot, flags, hw_data->fd, offset);
+}
+
+static int pcm_hw_munmap(void *data __attribute__((unused)), void *addr, size_t length)
+{
+ return munmap(addr, length);
+}
+
+static int pcm_hw_open(unsigned int card, unsigned int device,
+ unsigned int flags, void **data, struct snd_node *node)
+{
+ struct pcm_hw_data *hw_data;
+ char fn[256];
+ int fd;
+
+ hw_data = calloc(1, sizeof(*hw_data));
+ if (!hw_data) {
+ return -ENOMEM;
+ }
+
+ snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
+ flags & PCM_IN ? 'c' : 'p');
+ if (flags & PCM_NONBLOCK)
+ fd = open(fn, O_RDWR|O_NONBLOCK);
+ else
+ fd = open(fn, O_RDWR);
+
+ if (fd < 0) {
+ free(hw_data);
+ return fd;
+ }
+
+ hw_data->card = card;
+ hw_data->device = device;
+ hw_data->fd = fd;
+ hw_data->node = node;
+
+ *data = hw_data;
+
+ return fd;
+}
+
+const struct pcm_ops hw_ops = {
+ .open = pcm_hw_open,
+ .close = pcm_hw_close,
+ .ioctl = pcm_hw_ioctl,
+ .mmap = pcm_hw_mmap,
+ .munmap = pcm_hw_munmap,
+ .poll = pcm_hw_poll,
+};
+
diff --git a/src/pcm_io.h b/src/pcm_io.h
new file mode 100644
index 0000000..3c622fd
--- /dev/null
+++ b/src/pcm_io.h
@@ -0,0 +1,52 @@
+/* pcm_io.h
+** Copyright (c) 2019, The Linux Foundation.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+#ifndef TINYALSA_SRC_PCM_IO_H
+#define TINYALSA_SRC_PCM_IO_H
+
+#include <poll.h>
+#include <sound/asound.h>
+
+struct snd_node;
+
+struct pcm_ops {
+ int (*open) (unsigned int card, unsigned int device,
+ unsigned int flags, void **data, struct snd_node *node);
+ void (*close) (void *data);
+ int (*ioctl) (void *data, unsigned int cmd, ...);
+ void *(*mmap) (void *data, void *addr, size_t length, int prot, int flags,
+ off_t offset);
+ int (*munmap) (void *data, void *addr, size_t length);
+ int (*poll) (void *data, struct pollfd *pfd, nfds_t nfds, int timeout);
+};
+
+extern const struct pcm_ops hw_ops;
+extern const struct pcm_ops plug_ops;
+
+#endif /* TINYALSA_SRC_PCM_IO_H */
diff --git a/src/pcm_plugin.c b/src/pcm_plugin.c
new file mode 100644
index 0000000..15bfc80
--- /dev/null
+++ b/src/pcm_plugin.c
@@ -0,0 +1,778 @@
+/* pcm_plugin.c
+** Copyright (c) 2019, The Linux Foundation.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <poll.h>
+#include <dlfcn.h>
+
+#include <sys/ioctl.h>
+#include <linux/ioctl.h>
+#include <sound/asound.h>
+#include <tinyalsa/asoundlib.h>
+#include <tinyalsa/plugin.h>
+
+#include "pcm_io.h"
+#include "snd_card_plugin.h"
+
+/* 2 words of uint32_t = 64 bits of mask */
+#define PCM_MASK_SIZE (2)
+#define ARRAY_SIZE(a) \
+ (sizeof(a) / sizeof(a[0]))
+
+#define PCM_PARAM_GET_MASK(p, n) \
+ &p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK];
+
+enum {
+ PCM_PLUG_HW_PARAM_SELECT_MIN,
+ PCM_PLUG_HW_PARAM_SELECT_MAX,
+ PCM_PLUG_HW_PARAM_SELECT_VAL,
+};
+
+enum {
+ PCM_PLUG_STATE_OPEN,
+ PCM_PLUG_STATE_SETUP,
+ PCM_PLUG_STATE_PREPARED,
+ PCM_PLUG_STATE_RUNNING,
+};
+
+struct pcm_plug_data {
+ unsigned int card;
+ unsigned int device;
+ unsigned int fd;
+ unsigned int flags;
+
+ void *dl_hdl;
+ /** pointer to plugin operation */
+ const struct pcm_plugin_ops *ops;
+ struct pcm_plugin *plugin;
+ void *dev_node;
+};
+
+static unsigned int param_list[] = {
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ SNDRV_PCM_HW_PARAM_RATE,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ SNDRV_PCM_HW_PARAM_PERIODS,
+};
+
+static int convert_plugin_to_pcm_state(int plugin_state)
+{
+ switch (plugin_state) {
+ case PCM_PLUG_STATE_SETUP:
+ return PCM_STATE_SETUP;
+ case PCM_PLUG_STATE_RUNNING:
+ return PCM_STATE_RUNNING;
+ case PCM_PLUG_STATE_PREPARED:
+ return PCM_STATE_PREPARED;
+ case PCM_PLUG_STATE_OPEN:
+ return PCM_STATE_OPEN;
+ default:
+ break;
+ }
+
+ return PCM_STATE_OPEN;
+}
+
+static void pcm_plug_close(void *data)
+{
+ struct pcm_plug_data *plug_data = data;
+ struct pcm_plugin *plugin = plug_data->plugin;
+
+ plug_data->ops->close(plugin);
+ dlclose(plug_data->dl_hdl);
+
+ free(plug_data);
+}
+
+static int pcm_plug_info(struct pcm_plug_data *plug_data,
+ struct snd_pcm_info *info)
+{
+ int stream = SNDRV_PCM_STREAM_PLAYBACK;
+ int ret = 0, val = -1;
+ char *name;
+
+ memset(info, 0, sizeof(*info));
+
+ if (plug_data->flags & PCM_IN) {
+ stream = SNDRV_PCM_STREAM_CAPTURE;
+ ret = snd_utils_get_int(plug_data->dev_node, "capture", &val);
+ if (ret || !val) {
+ fprintf(stderr, "%s: not a capture device\n", __func__);
+ return -EINVAL;
+ }
+ } else {
+ stream = SNDRV_PCM_STREAM_PLAYBACK;
+ ret = snd_utils_get_int(plug_data->dev_node, "playback", &val);
+ if (ret || !val) {
+ fprintf(stderr, "%s: not a playback device\n", __func__);
+ return -EINVAL;
+ }
+ }
+
+ info->stream = stream;
+ info->card = plug_data->card;
+ info->device = plug_data->device;
+
+ ret = snd_utils_get_str(plug_data->dev_node, "name", &name);
+ if (ret) {
+ fprintf(stderr, "%s: failed to get pcm device name\n", __func__);
+ return ret;
+ }
+
+ strncpy((char *)info->id, name, sizeof(info->id));
+ strncpy((char *)info->name, name, sizeof(info->name));
+ strncpy((char *)info->subname, name, sizeof(info->subname));
+
+ info->subdevices_count = 1;
+
+ return ret;
+}
+
+static void pcm_plug_set_mask(struct snd_pcm_hw_params *p, int n, uint64_t v)
+{
+ struct snd_mask *mask;
+
+ mask = PCM_PARAM_GET_MASK(p, n);
+
+ mask->bits[0] |= (v & 0xFFFFFFFF);
+ mask->bits[1] |= ((v >> 32) & 0xFFFFFFFF);
+ /*
+ * currently only supporting 64 bits, may need to update to support
+ * more than 64 bits
+ */
+}
+
+static void pcm_plug_set_interval(struct snd_pcm_hw_params *params,
+ int p, struct pcm_plugin_min_max *v, int is_integer)
+{
+ struct snd_interval *i;
+
+ i = &params->intervals[p - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL];
+
+ i->min = v->min;
+ i->max = v->max;
+
+ if (is_integer)
+ i->integer = 1;
+}
+
+static int pcm_plug_frames_to_bytes(unsigned int frames,
+ unsigned int frame_bits)
+{
+ return (frames * (frame_bits / 8));
+}
+
+static int pcm_plug_bytes_to_frames(unsigned int size,
+ unsigned int frame_bits)
+{
+ return (size * 8) / frame_bits;
+}
+
+static int pcm_plug_get_params(struct pcm_plugin *plugin,
+ struct snd_pcm_hw_params *params)
+{
+ struct pcm_plugin_min_max bw, ch, pb, periods;
+ struct pcm_plugin_min_max val;
+ struct pcm_plugin_min_max frame_bits, buffer_bytes;
+
+ /*
+ * populate the struct snd_pcm_hw_params structure
+ * using the hw_param constraints provided by plugin
+ * via the plugin->constraints
+ */
+
+ /* Set the mask params */
+ pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_ACCESS,
+ plugin->constraints->access);
+ pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_FORMAT,
+ plugin->constraints->format);
+ pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT,
+ SNDRV_PCM_SUBFORMAT_STD);
+
+ /* Set the standard interval params */
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ &plugin->constraints->bit_width, 1);
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &plugin->constraints->channels, 1);
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_RATE,
+ &plugin->constraints->rate, 1);
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ &plugin->constraints->period_bytes, 0);
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIODS,
+ &plugin->constraints->periods, 1);
+
+ /* set the calculated interval params */
+
+ bw.min = plugin->constraints->bit_width.min;
+ bw.max = plugin->constraints->bit_width.max;
+
+ ch.min = plugin->constraints->channels.min;
+ ch.max = plugin->constraints->channels.max;
+
+ pb.min = plugin->constraints->period_bytes.min;
+ pb.max = plugin->constraints->period_bytes.max;
+
+ periods.min = plugin->constraints->periods.min;
+ periods.max = plugin->constraints->periods.max;
+
+ /* Calculate and set frame bits */
+ frame_bits.min = bw.min * ch.min;
+ frame_bits.max = bw.max * ch.max;
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS,
+ &frame_bits, 1);
+
+
+ /* Calculate and set period_size in frames */
+ val.min = pcm_plug_bytes_to_frames(pb.min, frame_bits.min);
+ val.max = pcm_plug_bytes_to_frames(pb.max, frame_bits.min);
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ &val, 1);
+
+ /* Calculate and set buffer_bytes */
+ buffer_bytes.min = pb.min * periods.min;
+ buffer_bytes.max = pb.max * periods.max;
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ &buffer_bytes, 1);
+
+ /* Calculate and set buffer_size in frames */
+ val.min = pcm_plug_bytes_to_frames(buffer_bytes.min, frame_bits.min);
+ val.max = pcm_plug_bytes_to_frames(buffer_bytes.max, frame_bits.min);
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+ &val, 1);
+ return 0;
+}
+
+static int pcm_plug_masks_refine(struct snd_pcm_hw_params *p,
+ struct snd_pcm_hw_params *c)
+{
+ struct snd_mask *req_mask;
+ struct snd_mask *con_mask;
+ unsigned int idx, i, masks;
+
+ masks = SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK;
+
+ for (idx = 0; idx <= masks; idx++) {
+
+ if (!(p->rmask & (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_MASK))))
+ continue;
+
+ req_mask = PCM_PARAM_GET_MASK(p, idx);
+ con_mask = PCM_PARAM_GET_MASK(c, idx);
+
+ /*
+ * set the changed mask if requested mask value is not the same as
+ * constrained mask value
+ */
+ if (memcmp(req_mask, con_mask, PCM_MASK_SIZE * sizeof(uint32_t)))
+ p->cmask |= 1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_MASK);
+
+ /* Actually change the requested mask to constrained mask */
+ for (i = 0; i < PCM_MASK_SIZE; i++)
+ req_mask->bits[i] &= con_mask->bits[i];
+ }
+
+ return 0;
+}
+
+static int pcm_plug_interval_refine(struct snd_pcm_hw_params *p,
+ struct snd_pcm_hw_params *c)
+{
+ struct snd_interval *ri;
+ struct snd_interval *ci;
+ unsigned int idx;
+ unsigned int intervals;
+ int changed = 0;
+
+ intervals = SNDRV_PCM_HW_PARAM_LAST_INTERVAL -
+ SNDRV_PCM_HW_PARAM_FIRST_INTERVAL;
+
+ for (idx = 0; idx <= intervals; idx++) {
+ ri = &p->intervals[idx];
+ ci = &c->intervals[idx];
+
+ if (!(p->rmask & (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_INTERVAL)) ))
+ continue;
+
+ if (ri->min < ci->min) {
+ ri->min = ci->min;
+ ri->openmin = ci->openmin;
+ changed = 1;
+ } else if (ri->min == ci->min && !ri->openmin && ci->openmin) {
+ ri->openmin = 1;
+ changed = 1;
+ }
+
+ if (ri->max > ci->max) {
+ ri->max = ci->max;
+ ri->openmax = ci->openmax;
+ changed = 1;
+ } else if (ri->max == ci->max && !ri->openmax && ci->openmax) {
+ ri->openmax = 1;
+ changed = 1;
+ };
+
+ if (!ri->integer && ci->integer) {
+ ri->integer = 1;
+ changed = 1;
+ }
+
+ if (ri->integer) {
+ if (ri->openmin) {
+ ri->min++;
+ ri->openmin = 0;
+ }
+ if (ri->openmax) {
+ ri->max--;
+ ri->openmax = 0;
+ }
+ } else if (!ri->openmin && !ri->openmax && ri->min == ri->max) {
+ ri->integer = 1;
+ }
+
+ /* Set the changed mask */
+ if (changed)
+ p->cmask |= (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_INTERVAL));
+ }
+
+ return 0;
+}
+
+
+static int pcm_plug_hw_params_refine(struct snd_pcm_hw_params *p,
+ struct snd_pcm_hw_params *c)
+{
+ int rc;
+
+ rc = pcm_plug_masks_refine(p, c);
+ if (rc) {
+ fprintf(stderr, "%s: masks refine failed %d\n", __func__, rc);
+ return rc;
+ }
+
+ rc = pcm_plug_interval_refine(p, c);
+ if (rc) {
+ fprintf(stderr, "%s: interval refine failed %d\n", __func__, rc);
+ return rc;
+ }
+
+ /* clear the requested params */
+ p->rmask = 0;
+
+ return rc;
+}
+
+static int __pcm_plug_hrefine(struct pcm_plug_data *plug_data,
+ struct snd_pcm_hw_params *params)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+ struct snd_pcm_hw_params plug_params;
+ int rc;
+
+ memset(&plug_params, 0, sizeof(plug_params));
+ rc = pcm_plug_get_params(plugin, &plug_params);
+ if (rc) {
+ fprintf(stderr, "%s: pcm_plug_get_params failed %d\n",
+ __func__, rc);
+ return -EINVAL;
+ }
+
+ return pcm_plug_hw_params_refine(params, &plug_params);
+
+}
+
+static int pcm_plug_hrefine(struct pcm_plug_data *plug_data,
+ struct snd_pcm_hw_params *params)
+{
+ return __pcm_plug_hrefine(plug_data, params);
+}
+
+static int pcm_plug_interval_select(struct snd_pcm_hw_params *p,
+ unsigned int param, unsigned int select, unsigned int val)
+{
+ struct snd_interval *i;
+
+ if (param < SNDRV_PCM_HW_PARAM_FIRST_INTERVAL ||
+ param > SNDRV_PCM_HW_PARAM_LAST_INTERVAL)
+ return -EINVAL;
+
+ i = &p->intervals[param - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL];
+
+ if (!i->min)
+ return -EINVAL;
+
+ switch (select) {
+
+ case PCM_PLUG_HW_PARAM_SELECT_MIN:
+ i->max = i->min;
+ break;
+
+ case PCM_PLUG_HW_PARAM_SELECT_MAX:
+ i->min = i->max;
+ break;
+
+ case PCM_PLUG_HW_PARAM_SELECT_VAL:
+ i->min = i->max = val;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void pcm_plug_hw_params_set(struct snd_pcm_hw_params *p)
+{
+ unsigned int i, select;
+ unsigned int bw, ch, period_sz, periods;
+ unsigned int val1, val2, offset;
+
+ offset = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL;
+
+ /* Select the min values first */
+ select = PCM_PLUG_HW_PARAM_SELECT_MIN;
+ for (i = 0; i < ARRAY_SIZE(param_list); i++)
+ pcm_plug_interval_select(p, param_list[i], select, 0);
+
+ /* Select calculated values */
+ select = PCM_PLUG_HW_PARAM_SELECT_VAL;
+ bw = (p->intervals[SNDRV_PCM_HW_PARAM_SAMPLE_BITS - offset]).min;
+ ch = (p->intervals[SNDRV_PCM_HW_PARAM_CHANNELS - offset]).min;
+ period_sz = (p->intervals[SNDRV_PCM_HW_PARAM_PERIOD_SIZE - offset]).min;
+ periods = (p->intervals[SNDRV_PCM_HW_PARAM_PERIODS - offset]).min;
+
+ val1 = bw * ch; // frame_bits;
+ pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_FRAME_BITS, select, val1);
+
+ val2 = pcm_plug_frames_to_bytes(period_sz, val1); // period_bytes;
+ pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, select,
+ val2);
+
+ val2 = period_sz * periods; //buffer_size;
+ pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, select, val2);
+
+ val2 = pcm_plug_frames_to_bytes(period_sz * periods, val1); //buffer_bytes;
+ pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, select, val2);
+}
+
+static int pcm_plug_hparams(struct pcm_plug_data *plug_data,
+ struct snd_pcm_hw_params *params)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+ int rc;
+
+ if (plugin->state != PCM_PLUG_STATE_OPEN)
+ return -EBADFD;
+
+ params->rmask = ~0U;
+
+ rc = __pcm_plug_hrefine(plug_data, params);
+ if (rc) {
+ fprintf(stderr, "%s: __pcm_plug_hrefine failed %d\n",
+ __func__, rc);
+ return rc;
+ }
+
+ pcm_plug_hw_params_set(params);
+
+ rc = plug_data->ops->hw_params(plugin, params);
+ if (!rc)
+ plugin->state = PCM_PLUG_STATE_SETUP;
+
+ return rc;
+}
+
+static int pcm_plug_sparams(struct pcm_plug_data *plug_data,
+ struct snd_pcm_sw_params *params)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+
+ if (plugin->state != PCM_PLUG_STATE_SETUP)
+ return -EBADFD;
+
+ return plug_data->ops->sw_params(plugin, params);
+}
+
+static int pcm_plug_sync_ptr(struct pcm_plug_data *plug_data,
+ struct snd_pcm_sync_ptr *sync_ptr)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+ int ret = -EBADFD;
+
+ if (plugin->state >= PCM_PLUG_STATE_SETUP) {
+ ret = plug_data->ops->sync_ptr(plugin, sync_ptr);
+ if (ret == 0)
+ sync_ptr->s.status.state = convert_plugin_to_pcm_state(plugin->state);
+ }
+
+ return ret;
+}
+
+static int pcm_plug_writei_frames(struct pcm_plug_data *plug_data,
+ struct snd_xferi *x)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+
+ if (plugin->state != PCM_PLUG_STATE_PREPARED &&
+ plugin->state != PCM_PLUG_STATE_RUNNING)
+ return -EBADFD;
+
+ return plug_data->ops->writei_frames(plugin, x);
+}
+
+static int pcm_plug_readi_frames(struct pcm_plug_data *plug_data,
+ struct snd_xferi *x)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+
+ if (plugin->state != PCM_PLUG_STATE_RUNNING)
+ return -EBADFD;
+
+ return plug_data->ops->readi_frames(plugin, x);
+}
+
+static int pcm_plug_ttstamp(struct pcm_plug_data *plug_data,
+ int *tstamp)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+
+ if (plugin->state < PCM_PLUG_STATE_SETUP)
+ return -EBADFD;
+
+ return plug_data->ops->ttstamp(plugin, tstamp);
+}
+
+static int pcm_plug_prepare(struct pcm_plug_data *plug_data)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+ int rc;
+
+ if (plugin->state != PCM_PLUG_STATE_SETUP)
+ return -EBADFD;
+
+ rc = plug_data->ops->prepare(plugin);
+ if (!rc)
+ plugin->state = PCM_PLUG_STATE_PREPARED;
+
+ return rc;
+}
+
+static int pcm_plug_start(struct pcm_plug_data *plug_data)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+ int rc;
+
+ if (plugin->state != PCM_PLUG_STATE_PREPARED)
+ return -EBADFD;
+
+ rc = plug_data->ops->start(plugin);
+ if (!rc)
+ plugin->state = PCM_PLUG_STATE_RUNNING;
+
+ return rc;
+}
+
+static int pcm_plug_drop(struct pcm_plug_data *plug_data)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+ int rc;
+
+ rc = plug_data->ops->drop(plugin);
+ if (!rc)
+ plugin->state = PCM_PLUG_STATE_SETUP;
+
+ return rc;
+}
+
+static int pcm_plug_ioctl(void *data, unsigned int cmd, ...)
+{
+ struct pcm_plug_data *plug_data = data;
+ struct pcm_plugin *plugin = plug_data->plugin;
+ int ret;
+ va_list ap;
+ void *arg;
+
+ va_start(ap, cmd);
+ arg = va_arg(ap, void *);
+ va_end(ap);
+
+ switch (cmd) {
+ case SNDRV_PCM_IOCTL_INFO:
+ ret = pcm_plug_info(plug_data, arg);
+ break;
+ case SNDRV_PCM_IOCTL_TTSTAMP:
+ ret = pcm_plug_ttstamp(plug_data, arg);
+ break;
+ case SNDRV_PCM_IOCTL_HW_REFINE:
+ ret = pcm_plug_hrefine(plug_data, arg);
+ break;
+ case SNDRV_PCM_IOCTL_HW_PARAMS:
+ ret = pcm_plug_hparams(plug_data, arg);
+ break;
+ case SNDRV_PCM_IOCTL_SW_PARAMS:
+ ret = pcm_plug_sparams(plug_data, arg);
+ break;
+ case SNDRV_PCM_IOCTL_SYNC_PTR:
+ ret = pcm_plug_sync_ptr(plug_data, arg);
+ break;
+ case SNDRV_PCM_IOCTL_PREPARE:
+ ret = pcm_plug_prepare(plug_data);
+ break;
+ case SNDRV_PCM_IOCTL_START:
+ ret = pcm_plug_start(plug_data);
+ break;
+ case SNDRV_PCM_IOCTL_DROP:
+ ret = pcm_plug_drop(plug_data);
+ break;
+ case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
+ ret = pcm_plug_writei_frames(plug_data, arg);
+ break;
+ case SNDRV_PCM_IOCTL_READI_FRAMES:
+ ret = pcm_plug_readi_frames(plug_data, arg);
+ break;
+ default:
+ ret = plug_data->ops->ioctl(plugin, cmd, arg);
+ break;
+ }
+
+ return ret;
+}
+
+static int pcm_plug_poll(void *data, struct pollfd *pfd, nfds_t nfds,
+ int timeout)
+{
+ struct pcm_plug_data *plug_data = data;
+ struct pcm_plugin *plugin = plug_data->plugin;
+
+ return plug_data->ops->poll(plugin, pfd, nfds, timeout);
+}
+
+static void *pcm_plug_mmap(void *data, void *addr, size_t length, int prot,
+ int flags, off_t offset)
+{
+ struct pcm_plug_data *plug_data = data;
+ struct pcm_plugin *plugin = plug_data->plugin;
+
+ if (plugin->state != PCM_PLUG_STATE_SETUP)
+ return NULL;
+
+ return plug_data->ops->mmap(plugin, addr, length, prot, flags, offset);
+}
+
+static int pcm_plug_munmap(void *data, void *addr, size_t length)
+{
+ struct pcm_plug_data *plug_data = data;
+ struct pcm_plugin *plugin = plug_data->plugin;
+
+ if (plugin->state != PCM_PLUG_STATE_SETUP)
+ return -EBADFD;
+
+ return plug_data->ops->munmap(plugin, addr, length);
+}
+
+static int pcm_plug_open(unsigned int card, unsigned int device,
+ unsigned int flags, void **data, struct snd_node *pcm_node)
+{
+ struct pcm_plug_data *plug_data;
+ void *dl_hdl;
+ int rc = 0;
+ char *so_name;
+
+ plug_data = calloc(1, sizeof(*plug_data));
+ if (!plug_data) {
+ return -ENOMEM;
+ }
+
+ rc = snd_utils_get_str(pcm_node, "so-name", &so_name);
+ if (rc) {
+ fprintf(stderr, "%s: failed to get plugin lib name\n", __func__);
+ goto err_get_lib;
+ }
+
+ dl_hdl = dlopen(so_name, RTLD_NOW);
+ if (!dl_hdl) {
+ fprintf(stderr, "%s: unable to open %s\n", __func__, so_name);
+ goto err_dl_open;
+ } else {
+ fprintf(stderr, "%s: dlopen successful for %s\n", __func__, so_name);
+ }
+
+ dlerror();
+
+ plug_data->ops = dlsym(dl_hdl, "pcm_plugin_ops");
+ if (!plug_data->ops) {
+ fprintf(stderr, "%s: dlsym to open fn failed, err = '%s'\n",
+ __func__, dlerror());
+ goto err_dlsym;
+ }
+
+ rc = plug_data->ops->open(&plug_data->plugin, card, device, flags);
+ if (rc) {
+ fprintf(stderr, "%s: failed to open plugin\n", __func__);
+ goto err_open;
+ }
+
+ plug_data->dl_hdl = dl_hdl;
+ plug_data->card = card;
+ plug_data->device = device;
+ plug_data->dev_node = pcm_node;
+ plug_data->flags = flags;
+
+ *data = plug_data;
+
+ plug_data->plugin->state = PCM_PLUG_STATE_OPEN;
+
+ return 0;
+
+err_open:
+err_dlsym:
+ dlclose(dl_hdl);
+err_get_lib:
+err_dl_open:
+ free(plug_data);
+
+ return rc;
+}
+
+const struct pcm_ops plug_ops = {
+ .open = pcm_plug_open,
+ .close = pcm_plug_close,
+ .ioctl = pcm_plug_ioctl,
+ .mmap = pcm_plug_mmap,
+ .munmap = pcm_plug_munmap,
+ .poll = pcm_plug_poll,
+};
diff --git a/src/snd_card_plugin.c b/src/snd_card_plugin.c
new file mode 100644
index 0000000..c6d4baf
--- /dev/null
+++ b/src/snd_card_plugin.c
@@ -0,0 +1,149 @@
+/* snd_card_plugin.c
+** Copyright (c) 2019, The Linux Foundation.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+#include "snd_card_plugin.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#define SND_DLSYM(h, p, s, err) \
+do { \
+ err = 0; \
+ p = dlsym(h, s); \
+ if (!p) \
+ err = -ENODEV; \
+} while(0)
+
+int snd_utils_get_int(struct snd_node *node, const char *prop, int *val)
+{
+ if (!node || !node->card_node || !node->dev_node)
+ return SND_NODE_TYPE_HW;
+
+ return node->ops->get_int(node->dev_node, prop, val);
+}
+
+int snd_utils_get_str(struct snd_node *node, const char *prop, char **val)
+{
+ if (!node || !node->card_node || !node->dev_node)
+ return SND_NODE_TYPE_HW;
+
+ return node->ops->get_str(node->dev_node, prop, val);
+}
+
+void snd_utils_close_dev_node(struct snd_node *node)
+{
+ if (!node)
+ return;
+
+ if (node->card_node)
+ node->ops->close_card(node->card_node);
+
+ if (node->dl_hdl)
+ dlclose(node->dl_hdl);
+
+ free(node);
+}
+
+enum snd_node_type snd_utils_get_node_type(struct snd_node *node)
+{
+ int val = SND_NODE_TYPE_HW;
+
+ if (!node || !node->card_node || !node->dev_node)
+ return SND_NODE_TYPE_HW;
+
+ node->ops->get_int(node->dev_node, "type", &val);
+
+ return val;
+}
+
+static int snd_utils_resolve_symbols(struct snd_node *node)
+{
+ void *dl = node->dl_hdl;
+ int err;
+ SND_DLSYM(dl, node->ops, "snd_card_ops", err);
+ return err;
+}
+
+static struct snd_node *snd_utils_open_dev_node(unsigned int card,
+ unsigned int device,
+ int dev_type)
+{
+ struct snd_node *node;
+ int rc = 0;
+
+ node = calloc(1, sizeof(*node));
+ if (!node)
+ return NULL;
+
+ node->dl_hdl = dlopen("libsndcardparser.so", RTLD_NOW);
+ if (!node->dl_hdl) {
+ goto err_dl_open;
+ }
+
+ rc = snd_utils_resolve_symbols(node);
+ if (rc < 0)
+ goto err_resolve_symbols;
+
+ node->card_node = node->ops->open_card(card);
+ if (!node->card_node)
+ goto err_resolve_symbols;
+
+ if (dev_type == NODE_PCM) {
+ node->dev_node = node->ops->get_pcm(node->card_node, device);
+ } else {
+ node->dev_node = node->ops->get_mixer(node->card_node);
+ }
+
+ if (!node->dev_node)
+ goto err_get_node;
+
+ return node;
+
+err_get_node:
+ node->ops->close_card(node->card_node);
+
+err_resolve_symbols:
+ dlclose(node->dl_hdl);
+
+err_dl_open:
+ free(node);
+ return NULL;
+}
+
+struct snd_node* snd_utils_open_pcm(unsigned int card,
+ unsigned int device)
+{
+ return snd_utils_open_dev_node(card, device, NODE_PCM);
+}
+
+struct snd_node* snd_utils_open_mixer(unsigned int card)
+{
+ return snd_utils_open_dev_node(card, 0, NODE_MIXER);
+}
diff --git a/src/snd_card_plugin.h b/src/snd_card_plugin.h
new file mode 100644
index 0000000..b80695e
--- /dev/null
+++ b/src/snd_card_plugin.h
@@ -0,0 +1,74 @@
+/* snd_utils.h
+** Copyright (c) 2019, The Linux Foundation.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+#ifndef TINYALSA_SRC_SND_CARD_UTILS_H
+#define TINYALSA_SRC_SND_CARD_UTILS_H
+
+#include <tinyalsa/plugin.h>
+
+#include <dlfcn.h>
+
+/** Encapsulates the pcm device definition from
+ * the sound card definition configuration file.
+ */
+struct snd_node {
+ /** Pointer the card definition */
+ void *card_node;
+ /** Pointer to device definition, either PCM or MIXER device */
+ void *dev_node;
+ /** Pointer to the sound card parser library */
+ void *dl_hdl;
+ /** A pointer to the operations structure. */
+ const struct snd_node_ops* ops;
+};
+
+enum snd_node_type {
+ SND_NODE_TYPE_HW = 0,
+ SND_NODE_TYPE_PLUGIN,
+ SND_NODE_TYPE_INVALID,
+};
+
+enum {
+ NODE_PCM,
+ NODE_MIXER
+};
+
+struct snd_node *snd_utils_open_pcm(unsigned int card, unsigned int device);
+
+struct snd_node *snd_utils_open_mixer(unsigned int card);
+
+void snd_utils_close_dev_node(struct snd_node *node);
+
+enum snd_node_type snd_utils_get_node_type(struct snd_node *node);
+
+int snd_utils_get_int(struct snd_node *node, const char *prop, int *val);
+
+int snd_utils_get_str(struct snd_node *node, const char *prop, char **val);
+
+#endif /* end of TINYALSA_SRC_SND_CARD_UTILS_H */
diff --git a/tests/include/pcm_test_device.h b/tests/include/pcm_test_device.h
new file mode 100644
index 0000000..7ced192
--- /dev/null
+++ b/tests/include/pcm_test_device.h
@@ -0,0 +1,54 @@
+/* pcm_test.h
+**
+** Copyright 2020, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+
+#ifndef TINYALSA_TESTS_PCM_TEST_H_
+#define TINYALSA_TESTS_PCM_TEST_H_
+
+namespace tinyalsa {
+namespace testing {
+
+#ifndef TEST_LOOPBACK_CARD
+#define TEST_LOOPBACK_CARD 2
+#endif
+
+#ifndef TEST_LOOPBACK_PLAYBACK_DEVICE
+#define TEST_LOOPBACK_PLAYBACK_DEVICE 0
+#endif
+
+#ifndef TEST_LOOPBACK_CAPTURE_DEVICE
+#define TEST_LOOPBACK_CAPTURE_DEVICE 1
+#endif
+
+constexpr unsigned int kLoopbackCard = TEST_LOOPBACK_CARD;
+constexpr unsigned int kLoopbackPlaybackDevice = TEST_LOOPBACK_PLAYBACK_DEVICE;
+constexpr unsigned int kLoopbackCaptureDevice = TEST_LOOPBACK_CAPTURE_DEVICE;
+
+} // namespace testing
+} // namespace tinyalsa
+
+#endif
diff --git a/tests/src/mixer_test.cc b/tests/src/mixer_test.cc
new file mode 100644
index 0000000..717269c
--- /dev/null
+++ b/tests/src/mixer_test.cc
@@ -0,0 +1,316 @@
+/* mixer_test.c
+**
+** Copyright 2020, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+#include "pcm_test_device.h"
+
+#include <string_view>
+#include <string>
+#include <thread>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <gtest/gtest.h>
+
+#include "tinyalsa/mixer.h"
+
+namespace tinyalsa {
+namespace testing {
+
+#ifndef MAX_CARD_INDEX
+#define MAX_CARD_INDEX 2
+#endif
+
+static constexpr unsigned int kMaxCardIndex = MAX_CARD_INDEX;
+
+static constexpr int k100Percent = 100;
+static constexpr int k0Percent = 0;
+
+TEST(MixerTest, OpenAndClose) {
+ ASSERT_EQ(mixer_open(1000), nullptr);
+ mixer_close(nullptr);
+}
+
+class MixerTest : public ::testing::TestWithParam<unsigned int> {
+ protected:
+ MixerTest() : mixer_object(nullptr) {}
+ virtual ~MixerTest() = default;
+
+ virtual void SetUp() override {
+ unsigned int card = GetParam();
+ mixer_object = mixer_open(card);
+ ASSERT_NE(mixer_object, nullptr);
+ }
+
+ virtual void TearDown() override {
+ mixer_close(mixer_object);
+ }
+
+ mixer *mixer_object;
+};
+
+TEST_P(MixerTest, AddNewControls) {
+ ASSERT_EQ(mixer_add_new_ctls(mixer_object), 0);
+}
+
+TEST_P(MixerTest, GetName) {
+ const char *name = mixer_get_name(mixer_object);
+ std::cout << name << std::endl;
+ ASSERT_STRNE(name, "");
+}
+
+TEST_P(MixerTest, GetNumberOfControls) {
+ unsigned int nums = mixer_get_num_ctls(mixer_object);
+ std::cout << nums << std::endl;
+ ASSERT_GT(nums, 0);
+}
+
+class MixerControlsTest : public MixerTest {
+ protected:
+ MixerControlsTest() : number_of_controls(0), controls(nullptr) {}
+ virtual ~MixerControlsTest() = default;
+
+ virtual void SetUp() override {
+ MixerTest::SetUp();
+
+ number_of_controls = mixer_get_num_ctls(mixer_object);
+ ASSERT_GT(number_of_controls, 0);
+
+ controls = std::make_unique<const mixer_ctl *[]>(number_of_controls);
+ ASSERT_NE(controls, nullptr);
+
+ for (unsigned int i = 0; i < number_of_controls; i++) {
+ controls[i] = mixer_get_ctl_const(mixer_object, i);
+ ASSERT_EQ(mixer_ctl_get_id(controls[i]), i);
+ ASSERT_STRNE(mixer_ctl_get_name(controls[i]), "");
+ ASSERT_NE(controls[i], nullptr);
+ }
+ }
+
+ virtual void TearDown() override {
+ controls = nullptr;
+ MixerTest::TearDown();
+ }
+
+ unsigned int number_of_controls;
+ std::unique_ptr<const mixer_ctl *[]> controls;
+};
+
+TEST_P(MixerControlsTest, GetNumberOfControlsByName) {
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ const char *name = mixer_ctl_get_name(controls[i]);
+ ASSERT_GE(mixer_get_num_ctls_by_name(mixer_object, name), 1);
+ }
+
+ std::string name{mixer_ctl_get_name(controls[0])};
+ name += "1";
+ ASSERT_EQ(mixer_get_num_ctls_by_name(mixer_object, name.c_str()), 0);
+}
+
+TEST_P(MixerControlsTest, GetControlById) {
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ ASSERT_EQ(mixer_get_ctl(mixer_object, i), controls[i]);
+ }
+
+ ASSERT_EQ(mixer_get_ctl(mixer_object, number_of_controls), nullptr);
+}
+
+TEST_P(MixerControlsTest, GetControlByName) {
+ std::unordered_set<std::string> visited_names_set;
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ std::string name{mixer_ctl_get_name(controls[i])};
+ if (visited_names_set.find(name) == visited_names_set.end()) {
+ ASSERT_EQ(mixer_get_ctl_by_name(mixer_object, name.c_str()), controls[i]);
+ visited_names_set.insert(name);
+ }
+ }
+}
+
+TEST_P(MixerControlsTest, GetControlByNameAndIndex) {
+ std::unordered_map<std::string, int32_t> visited_names_and_count_map;
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ std::string name{mixer_ctl_get_name(controls[i])};
+ if (visited_names_and_count_map.find(name) == visited_names_and_count_map.end()) {
+ visited_names_and_count_map[name] = 0;
+ }
+ ASSERT_EQ(
+ mixer_get_ctl_by_name_and_index(mixer_object,
+ name.c_str(),
+ visited_names_and_count_map[name]),
+ controls[i]);
+ visited_names_and_count_map[name] = visited_names_and_count_map[name] + 1;
+ }
+}
+
+static inline bool IsValidTypeString(std::string& type) {
+ return type == "BOOL" || type == "INT" || type == "ENUM" || type == "BYTE" ||
+ type == "IEC958" || type == "INT64";
+}
+
+TEST_P(MixerControlsTest, GetControlTypeString) {
+ ASSERT_STREQ(mixer_ctl_get_type_string(nullptr), "");
+
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ std::string type{mixer_ctl_get_type_string(controls[i])};
+ ASSERT_TRUE(IsValidTypeString(type));
+ }
+}
+
+TEST_P(MixerControlsTest, GetNumberOfValues) {
+ ASSERT_EQ(mixer_ctl_get_num_values(nullptr), 0);
+}
+
+TEST_P(MixerControlsTest, GetNumberOfEnumsAndEnumString) {
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ const mixer_ctl *control = controls[i];
+ if (mixer_ctl_get_type(control) == MIXER_CTL_TYPE_ENUM) {
+ unsigned int number_of_enums = mixer_ctl_get_num_enums(control);
+ ASSERT_GT(number_of_enums, 0);
+ for (unsigned int enum_id = 0; enum_id < number_of_enums; ++enum_id) {
+ const char *enum_name = mixer_ctl_get_enum_string(
+ const_cast<mixer_ctl *>(control),
+ enum_id);
+ ASSERT_STRNE(enum_name, "");
+ }
+ }
+ }
+}
+
+TEST_P(MixerControlsTest, UpdateControl) {
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ mixer_ctl_update(const_cast<mixer_ctl *>(controls[i]));
+ }
+}
+
+TEST_P(MixerControlsTest, GetPercent) {
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ const mixer_ctl *control = controls[i];
+ if (mixer_ctl_get_type(control) == MIXER_CTL_TYPE_INT) {
+ unsigned int number_of_values = mixer_ctl_get_num_values(controls[i]);
+ std::unique_ptr<long []> values = std::make_unique<long []>(number_of_values);
+ mixer_ctl_get_array(control, values.get(), number_of_values);
+ for (unsigned int value_id = 0; value_id < number_of_values; ++value_id) {
+ int max = mixer_ctl_get_range_max(control);
+ int min = mixer_ctl_get_range_min(control);
+ int percent = mixer_ctl_get_percent(control, value_id);
+ ASSERT_GE(percent, k0Percent);
+ ASSERT_LE(percent, k100Percent);
+ int range = max - min;
+ ASSERT_EQ(percent, (values[value_id] - min) * k100Percent / range);
+ }
+ } else {
+ ASSERT_EQ(mixer_ctl_get_percent(control, 0), -EINVAL);
+ }
+ }
+}
+
+TEST_P(MixerControlsTest, SetPercent) {
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ const mixer_ctl *control = controls[i];
+ if (mixer_ctl_get_type(control) == MIXER_CTL_TYPE_INT) {
+ unsigned int number_of_values = mixer_ctl_get_num_values(controls[i]);
+ std::unique_ptr<long []> values = std::make_unique<long []>(number_of_values);
+ mixer_ctl_get_array(control, values.get(), number_of_values);
+ for (unsigned int value_id = 0; value_id < number_of_values; ++value_id) {
+ int max = mixer_ctl_get_range_max(control);
+ int min = mixer_ctl_get_range_min(control);
+ int value = values[value_id];
+ int percent = mixer_ctl_get_percent(control, value_id);
+ if (mixer_ctl_set_percent(
+ const_cast<mixer_ctl *>(control), value_id, k100Percent) == 0) {
+ // note: some controls are able to be written, but their values might not be
+ // changed.
+ mixer_ctl_get_array(control, values.get(), number_of_values);
+ int new_value = values[value_id];
+ ASSERT_TRUE(new_value == value || new_value == max);
+ }
+ if (mixer_ctl_set_percent(
+ const_cast<mixer_ctl *>(control), value_id, k0Percent) == 0) {
+ mixer_ctl_get_array(control, values.get(), number_of_values);
+ int new_value = values[value_id];
+ ASSERT_TRUE(new_value == value || new_value == min);
+ }
+ mixer_ctl_set_percent(const_cast<mixer_ctl *>(control), value_id, percent);
+ }
+ } else {
+ ASSERT_EQ(mixer_ctl_get_percent(control, 0), -EINVAL);
+ }
+ }
+}
+
+TEST_P(MixerControlsTest, Event) {
+ ASSERT_EQ(mixer_subscribe_events(mixer_object, 1), 0);
+ const mixer_ctl *control = nullptr;
+ for (unsigned int i = 0; i < number_of_controls; ++i) {
+ std::string_view name{mixer_ctl_get_name(controls[i])};
+
+ if (name.find("Volume") != std::string_view::npos) {
+ control = controls[i];
+ }
+ }
+
+ if (control == nullptr) {
+ GTEST_SKIP() << "No volume control was found in the controls list.";
+ }
+
+ auto *local_mixer_object = mixer_object;
+ int percent = mixer_ctl_get_percent(control, 0);
+ std::thread thread([local_mixer_object, control, percent] () {
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ mixer_ctl_set_percent(
+ const_cast<mixer_ctl *>(control), 0,
+ percent == k100Percent ? k0Percent : k100Percent);
+ });
+
+ EXPECT_EQ(mixer_wait_event(mixer_object, 1000), 1);
+
+ EXPECT_EQ(mixer_consume_event(mixer_object), 0);
+
+ thread.join();
+ ASSERT_EQ(mixer_subscribe_events(mixer_object, 0), 0);
+
+ mixer_ctl_set_percent(const_cast<mixer_ctl *>(control), 0, percent);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ MixerTest,
+ MixerTest,
+ ::testing::Range<unsigned int>(
+ 0,
+ kMaxCardIndex + 1
+ ));
+
+INSTANTIATE_TEST_SUITE_P(
+ MixerControlsTest,
+ MixerControlsTest,
+ ::testing::Range<unsigned int>(
+ 0,
+ kMaxCardIndex + 1
+ ));
+
+} // namespace testing
+} // namespace tinyalsa
diff --git a/tests/src/pcm_in_test.cc b/tests/src/pcm_in_test.cc
new file mode 100644
index 0000000..e912abb
--- /dev/null
+++ b/tests/src/pcm_in_test.cc
@@ -0,0 +1,114 @@
+/* pcm_in_test.c
+**
+** Copyright 2020, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+#include "pcm_test_device.h"
+
+#include <chrono>
+#include <cstring>
+#include <iostream>
+
+#include <gtest/gtest.h>
+
+#include "tinyalsa/pcm.h"
+
+namespace tinyalsa {
+namespace testing {
+
+class PcmInTest : public ::testing::Test {
+ protected:
+ PcmInTest() : pcm_object(nullptr) {}
+ virtual ~PcmInTest() = default;
+
+ virtual void SetUp() override {
+ pcm_object = pcm_open(kLoopbackCard, kLoopbackCaptureDevice, PCM_IN, &kDefaultConfig);
+ ASSERT_NE(pcm_object, nullptr);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ }
+
+ virtual void TearDown() override {
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+ }
+
+ static constexpr unsigned int kDefaultChannels = 2;
+ static constexpr unsigned int kDefaultSamplingRate = 48000;
+ static constexpr unsigned int kDefaultPeriodSize = 1024;
+ static constexpr unsigned int kDefaultPeriodCount = 3;
+ static constexpr pcm_config kDefaultConfig = {
+ .channels = kDefaultChannels,
+ .rate = kDefaultSamplingRate,
+ .period_size = kDefaultPeriodSize,
+ .period_count = kDefaultPeriodCount,
+ .format = PCM_FORMAT_S16_LE,
+ .start_threshold = 0,
+ .stop_threshold = 0,
+ .silence_threshold = 0,
+ .silence_size = 0,
+ };
+
+ pcm* pcm_object;
+};
+
+TEST_F(PcmInTest, GetDelay) {
+ long delay = pcm_get_delay(pcm_object);
+ std::cout << delay << std::endl;
+ ASSERT_GE(delay, 0);
+}
+
+TEST_F(PcmInTest, Readi) {
+ constexpr uint32_t read_count = 20;
+
+ size_t buffer_size = pcm_frames_to_bytes(pcm_object, kDefaultConfig.period_size);
+ auto buffer = std::make_unique<char[]>(buffer_size);
+
+ int read_frames = 0;
+ unsigned int frames = pcm_bytes_to_frames(pcm_object, buffer_size);
+ auto start = std::chrono::steady_clock::now();
+ for (uint32_t i = 0; i < read_count; ++i) {
+ read_frames = pcm_readi(pcm_object, buffer.get(), frames);
+ ASSERT_EQ(read_frames, frames);
+ }
+
+ std::chrono::duration<double> difference = std::chrono::steady_clock::now() - start;
+ std::chrono::milliseconds expected_elapsed_time_ms(frames * read_count /
+ (kDefaultConfig.rate / 1000));
+
+ std::cout << difference.count() << std::endl;
+ std::cout << expected_elapsed_time_ms.count() << std::endl;
+
+ ASSERT_NEAR(difference.count() * 1000, expected_elapsed_time_ms.count(), 100);
+}
+
+TEST_F(PcmInTest, Writei) {
+ size_t buffer_size = pcm_frames_to_bytes(pcm_object, kDefaultConfig.period_size);
+ auto buffer = std::make_unique<char[]>(buffer_size);
+
+ unsigned int frames = pcm_bytes_to_frames(pcm_object, buffer_size);
+ ASSERT_EQ(pcm_writei(pcm_object, buffer.get(), frames), -EINVAL);
+}
+
+} // namespace testing
+} // namespace tinyalsa
diff --git a/tests/src/pcm_loopback_test.cc b/tests/src/pcm_loopback_test.cc
new file mode 100644
index 0000000..6a3ffb8
--- /dev/null
+++ b/tests/src/pcm_loopback_test.cc
@@ -0,0 +1,230 @@
+/* pcm_loopback_test.c
+**
+** Copyright 2020, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+#include "pcm_test_device.h"
+
+#include <chrono>
+#include <cmath>
+#include <cstring>
+#include <iostream>
+#include <thread>
+
+#include <gtest/gtest.h>
+
+#include "tinyalsa/pcm.h"
+
+namespace tinyalsa {
+namespace testing {
+
+template<int32_t CH, int32_t SR, pcm_format F>
+class SilenceGenerator {
+public:
+ pcm_format GetFormat() {
+ return F;
+ }
+
+ int32_t GetChannels() {
+ return CH;
+ };
+
+ int32_t GetSamplingRate() {
+ return SR;
+ };
+
+ virtual int32_t Read(void *buffer, int32_t size) {
+ std::memset(buffer, 0, size);
+ return size;
+ }
+};
+
+template<pcm_format F>
+struct PcmFormat {
+ using Type = void;
+ static constexpr int32_t kMax = 0;
+ static constexpr int32_t kMin = 0;
+};
+
+template<>
+struct PcmFormat<PCM_FORMAT_S16_LE> {
+ using Type = int16_t;
+ static constexpr Type kMax = std::numeric_limits<Type>::max();
+ static constexpr Type kMin = std::numeric_limits<Type>::min();
+};
+
+// CH: channels
+// SR: sampling rate
+// FQ: sine wave frequency
+// L: max level
+template<int32_t CH, int32_t SR, int32_t FQ, int32_t L, pcm_format F>
+class SineToneGenerator : public SilenceGenerator<CH, SR, F> {
+private:
+ using Type = typename PcmFormat<F>::Type;
+ static constexpr double kPi = M_PI;
+ static constexpr double kStep = FQ * CH * kPi / SR;
+
+ double channels[CH];
+ double gain;
+
+ Type GetSample(double radian) {
+ double sine = std::sin(radian) * gain;
+ if (sine >= 1.0) {
+ return PcmFormat<F>::kMax;
+ } else if (sine <= -1.0) {
+ return PcmFormat<F>::kMin;
+ }
+ return static_cast<Type>(sine * PcmFormat<F>::kMax);
+ }
+
+public:
+ SineToneGenerator() {
+ constexpr double phase = (CH == 1) ? 0 : kPi / 2 / (CH - 1);
+
+ channels[0] = 0.0;
+ for (int32_t i = 1; i < CH; ++i) {
+ channels[i] = channels[i - 1] + phase;
+ }
+
+ gain = std::pow(M_E, std::log(10) * static_cast<double>(L) / 20.0);
+ }
+
+ ~SineToneGenerator() = default;
+
+ int32_t Read(void *buffer, int32_t size) override {
+ Type *pcm_buffer = reinterpret_cast<Type *>(buffer);
+
+ size = (size / (CH * sizeof(Type))) * (CH * sizeof(Type));
+ int32_t samples = size / sizeof(Type);
+ int32_t s = 0;
+
+ while (s < samples) {
+ for (int32_t i = 0; i < CH; ++i) {
+ pcm_buffer[s++] = GetSample(channels[i]);
+ channels[i] += kStep;
+ }
+ }
+ return size;
+ }
+};
+
+template<typename T>
+static double Energy(T *buffer, size_t samples) {
+ double sum = 0.0;
+ for (size_t i = 0; i < samples; i++) {
+ sum += static_cast<double>(buffer[i]) * static_cast<double>(buffer[i]);
+ }
+ return sum;
+}
+
+TEST(PcmLoopbackTest, LoopbackS16le) {
+ static constexpr unsigned int kDefaultChannels = 2;
+ static constexpr unsigned int kDefaultSamplingRate = 48000;
+ static constexpr unsigned int kDefaultPeriodSize = 1024;
+ static constexpr unsigned int kDefaultPeriodCount = 3;
+ static constexpr unsigned int kDefaultPeriodTimeInMs =
+ kDefaultPeriodSize * 1000 / kDefaultSamplingRate;
+
+ static constexpr pcm_config kInConfig = {
+ .channels = kDefaultChannels,
+ .rate = kDefaultSamplingRate,
+ .period_size = kDefaultPeriodSize,
+ .period_count = kDefaultPeriodCount,
+ .format = PCM_FORMAT_S16_LE,
+ .start_threshold = 0,
+ .stop_threshold = 0,
+ .silence_threshold = 0,
+ .silence_size = 0,
+ };
+ pcm *pcm_in = pcm_open(kLoopbackCard, kLoopbackCaptureDevice, PCM_IN, &kInConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_in));
+
+ static constexpr pcm_config kOutConfig = {
+ .channels = kDefaultChannels,
+ .rate = kDefaultSamplingRate,
+ .period_size = kDefaultPeriodSize,
+ .period_count = kDefaultPeriodCount,
+ .format = PCM_FORMAT_S16_LE,
+ .start_threshold = kDefaultPeriodSize,
+ .stop_threshold = kDefaultPeriodSize * kDefaultPeriodCount,
+ .silence_threshold = 0,
+ .silence_size = 0,
+ };
+ pcm *pcm_out = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT, &kOutConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_out));
+
+ ASSERT_EQ(pcm_link(pcm_in, pcm_out), 0);
+
+ bool stopping = false;
+ ASSERT_EQ(pcm_get_subdevice(pcm_in), pcm_get_subdevice(pcm_out));
+
+ std::thread capture([pcm_in, &stopping] {
+ size_t buffer_size = pcm_frames_to_bytes(pcm_in, kDefaultPeriodSize);
+ unsigned int frames = pcm_bytes_to_frames(pcm_in, buffer_size);
+ auto buffer = std::make_unique<unsigned char[]>(buffer_size);
+ int32_t counter = 0;
+ while (!stopping) {
+ int res = pcm_readi(pcm_in, buffer.get(), frames);
+ if (res == -1) {
+ std::cout << pcm_get_error(pcm_in) << std::endl;
+ std::this_thread::sleep_for(std::chrono::milliseconds(kDefaultPeriodTimeInMs));
+ continue;
+ }
+ EXPECT_EQ(pcm_readi(pcm_in, buffer.get(), frames), frames) << counter;
+ // Test the energy of the buffer after the sine tone samples fill in the buffer.
+ // Therefore, check the buffer 5 times later.
+ if (counter >= 5) {
+ double e = Energy(buffer.get(), frames * kInConfig.channels);
+ EXPECT_GT(e, 0.0) << counter;
+ }
+ counter++;
+ }
+ });
+
+ std::thread playback([pcm_out, &stopping] {
+ SineToneGenerator<2, 48000, 1000, 0, PCM_FORMAT_S16_LE> generator;
+ size_t buffer_size = pcm_frames_to_bytes(pcm_out, kDefaultPeriodSize);
+ unsigned int frames = pcm_bytes_to_frames(pcm_out, buffer_size);
+ auto buffer = std::make_unique<unsigned char[]>(buffer_size);
+ int32_t counter = 0;
+ while (!stopping) {
+ generator.Read(buffer.get(), buffer_size);
+ EXPECT_EQ(pcm_writei(pcm_out, buffer.get(), frames), frames) << counter;
+ counter++;
+ }
+ });
+
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ stopping = true;
+ capture.join();
+ playback.join();
+
+ ASSERT_EQ(pcm_unlink(pcm_in), 0);
+ pcm_close(pcm_in);
+ pcm_close(pcm_out);
+}
+
+} // namespace testing
+} // namespace tinyalsa
diff --git a/tests/src/pcm_out_test.cc b/tests/src/pcm_out_test.cc
new file mode 100644
index 0000000..cbc6983
--- /dev/null
+++ b/tests/src/pcm_out_test.cc
@@ -0,0 +1,216 @@
+/* pcm_out_test.c
+**
+** Copyright 2020, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+#include "pcm_test_device.h"
+
+#include <chrono>
+#include <cstring>
+#include <iostream>
+
+#include <gtest/gtest.h>
+
+#include "tinyalsa/pcm.h"
+
+namespace tinyalsa {
+namespace testing {
+
+class PcmOutTest : public ::testing::Test {
+ protected:
+ PcmOutTest() : pcm_object(nullptr) {}
+ virtual ~PcmOutTest() = default;
+
+ virtual void SetUp() override {
+ pcm_object = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT, &kDefaultConfig);
+ ASSERT_NE(pcm_object, nullptr);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ }
+
+ virtual void TearDown() override {
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+ }
+
+ static constexpr unsigned int kDefaultChannels = 2;
+ static constexpr unsigned int kDefaultSamplingRate = 48000;
+ static constexpr unsigned int kDefaultPeriodSize = 1024;
+ static constexpr unsigned int kDefaultPeriodCount = 3;
+ static constexpr pcm_config kDefaultConfig = {
+ .channels = kDefaultChannels,
+ .rate = kDefaultSamplingRate,
+ .period_size = kDefaultPeriodSize,
+ .period_count = kDefaultPeriodCount,
+ .format = PCM_FORMAT_S16_LE,
+ .start_threshold = kDefaultPeriodSize,
+ .stop_threshold = kDefaultPeriodSize * kDefaultPeriodCount,
+ .silence_threshold = 0,
+ .silence_size = 0,
+ };
+
+ pcm* pcm_object;
+};
+
+TEST_F(PcmOutTest, GetFileDescriptor) {
+ ASSERT_GT(pcm_get_file_descriptor(pcm_object), 0);
+}
+
+TEST_F(PcmOutTest, GetChannels) {
+ ASSERT_EQ(pcm_get_channels(pcm_object), kDefaultConfig.channels);
+}
+
+TEST_F(PcmOutTest, GetSamplingRate) {
+ ASSERT_EQ(pcm_get_rate(pcm_object), kDefaultConfig.rate);
+}
+
+TEST_F(PcmOutTest, GetFormat) {
+ ASSERT_EQ(pcm_get_format(pcm_object), kDefaultConfig.format);
+
+}
+
+TEST_F(PcmOutTest, GetErrorMessage) {
+ ASSERT_STREQ(pcm_get_error(pcm_object), "");
+}
+
+TEST_F(PcmOutTest, GetConfig) {
+ ASSERT_EQ(pcm_get_config(nullptr), nullptr);
+ ASSERT_EQ(std::memcmp(pcm_get_config(pcm_object), &kDefaultConfig, sizeof(pcm_config)), 0);
+}
+
+TEST_F(PcmOutTest, SetConfig) {
+ ASSERT_EQ(pcm_set_config(nullptr, nullptr), -EFAULT);
+ ASSERT_EQ(pcm_set_config(pcm_object, nullptr), 0);
+}
+
+TEST_F(PcmOutTest, GetBufferSize) {
+ unsigned int buffer_size = pcm_get_buffer_size(pcm_object);
+ ASSERT_EQ(buffer_size, kDefaultConfig.period_count * kDefaultConfig.period_size);
+}
+
+TEST_F(PcmOutTest, FramesBytesConvert) {
+ unsigned int bytes = pcm_frames_to_bytes(pcm_object, 1);
+ ASSERT_EQ(bytes, pcm_format_to_bits(kDefaultConfig.format) / 8 * kDefaultConfig.channels);
+
+ unsigned int frames = pcm_bytes_to_frames(pcm_object, bytes + 1);
+ ASSERT_EQ(frames, 1);
+}
+
+TEST_F(PcmOutTest, GetAvailableAndTimestamp) {
+ unsigned int available = 0;
+ timespec time = { 0 };
+
+ ASSERT_LT(pcm_get_htimestamp(nullptr, nullptr, nullptr), 0);
+
+ ASSERT_EQ(pcm_get_htimestamp(pcm_object, &available, &time), 0);
+ ASSERT_NE(available, 0);
+ // ASSERT_NE(time.tv_nsec | time.tv_sec, 0);
+}
+
+TEST_F(PcmOutTest, GetSubdevice) {
+ ASSERT_EQ(pcm_get_subdevice(pcm_object), 0);
+}
+
+TEST_F(PcmOutTest, Readi) {
+ size_t buffer_size = pcm_frames_to_bytes(pcm_object, kDefaultConfig.period_size);
+ auto buffer = std::make_unique<char[]>(buffer_size);
+
+ unsigned int frames = pcm_bytes_to_frames(pcm_object, buffer_size);
+ ASSERT_EQ(pcm_readi(pcm_object, buffer.get(), frames), -EINVAL);
+}
+
+TEST_F(PcmOutTest, Writei) {
+ constexpr uint32_t write_count = 20;
+
+ size_t buffer_size = pcm_frames_to_bytes(pcm_object, kDefaultConfig.period_size);
+ auto buffer = std::make_unique<char[]>(buffer_size);
+ for (uint32_t i = 0; i < buffer_size; ++i) {
+ buffer[i] = static_cast<char>(i);
+ }
+
+ int written_frames = 0;
+ unsigned int frames = pcm_bytes_to_frames(pcm_object, buffer_size);
+ auto start = std::chrono::steady_clock::now();
+ for (uint32_t i = 0; i < write_count; ++i) {
+ written_frames = pcm_writei(pcm_object, buffer.get(), frames);
+ ASSERT_EQ(written_frames, frames);
+ }
+
+ std::chrono::duration<double> difference = std::chrono::steady_clock::now() - start;
+ std::chrono::milliseconds expected_elapsed_time_ms(frames *
+ (write_count - kDefaultConfig.period_count) / (kDefaultConfig.rate / 1000));
+
+ std::cout << difference.count() << std::endl;
+ std::cout << expected_elapsed_time_ms.count() << std::endl;
+
+ ASSERT_NEAR(difference.count() * 1000, expected_elapsed_time_ms.count(), 100);
+}
+
+class PcmOutMmapTest : public PcmOutTest {
+ protected:
+ PcmOutMmapTest() = default;
+ ~PcmOutMmapTest() = default;
+
+ virtual void SetUp() override {
+ pcm_object = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT | PCM_MMAP,
+ &kDefaultConfig);
+ ASSERT_NE(pcm_object, nullptr);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ }
+
+ virtual void TearDown() override {
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+ }
+};
+
+TEST_F(PcmOutMmapTest, Write) {
+ constexpr uint32_t write_count = 20;
+
+ size_t buffer_size = pcm_frames_to_bytes(pcm_object, kDefaultConfig.period_size);
+ auto buffer = std::make_unique<char[]>(buffer_size);
+ for (uint32_t i = 0; i < buffer_size; ++i) {
+ buffer[i] = static_cast<char>(i);
+ }
+
+ int written_frames = 0;
+ unsigned int frames = pcm_bytes_to_frames(pcm_object, buffer_size);
+ pcm_start(pcm_object);
+ auto start = std::chrono::steady_clock::now();
+ for (uint32_t i = 0; i < write_count; ++i) {
+ written_frames = pcm_mmap_write(pcm_object, buffer.get(), buffer_size);
+ ASSERT_EQ(written_frames, frames);
+ }
+ pcm_stop(pcm_object);
+
+ std::chrono::duration<double> difference = std::chrono::steady_clock::now() - start;
+ std::chrono::milliseconds expected_elapsed_time_ms(frames *
+ (write_count - kDefaultConfig.period_count) / (kDefaultConfig.rate / 1000));
+
+ std::cout << difference.count() << std::endl;
+ std::cout << expected_elapsed_time_ms.count() << std::endl;
+
+ ASSERT_NEAR(difference.count() * 1000, expected_elapsed_time_ms.count(), 100);
+}
+
+} // namespace testing
+} // namespace tinyalsa
diff --git a/tests/src/pcm_params_test.cc b/tests/src/pcm_params_test.cc
new file mode 100644
index 0000000..c8151e1
--- /dev/null
+++ b/tests/src/pcm_params_test.cc
@@ -0,0 +1,222 @@
+/* pcm_params_test.c
+**
+** Copyright 2020, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+
+#include "pcm_test_device.h"
+
+#include <cstring>
+#include <iostream>
+#include <memory>
+
+#include <gtest/gtest.h>
+
+#include "tinyalsa/pcm.h"
+
+namespace tinyalsa {
+namespace testing {
+
+static inline unsigned int OrAllBits(const pcm_mask *mask) {
+ static constexpr size_t kTotalMaskBytes = 32;
+ unsigned int res = 0;
+ for (uint32_t i = 0; i < kTotalMaskBytes / sizeof(pcm_mask::bits[0]); ++i) {
+ res |= mask->bits[i];
+ }
+ return res;
+}
+
+TEST(PcmParamsTest, GetAndFreeParams) {
+ pcm_params *params = nullptr;
+
+ // test to get nonexistent card and device.
+ params = pcm_params_get(1000, 1000, PCM_IN);
+ ASSERT_EQ(params, nullptr);
+
+ // test free null params.
+ pcm_params_free(params);
+
+ // assume that card 0, device 0 is always available.
+ params = pcm_params_get(0, 0, PCM_OUT);
+ ASSERT_NE(params, nullptr);
+ pcm_params_free(params);
+}
+
+TEST(PcmParamsTest, GetParamsBitMask) {
+ // test to get mask with null params
+ ASSERT_EQ(pcm_params_get_mask(nullptr, PCM_PARAM_ACCESS), nullptr);
+
+ // assume that card 0, device 0 is always available.
+ pcm_params *params = pcm_params_get(0, 0, PCM_OUT);
+ ASSERT_NE(params, nullptr);
+
+ // test to get param which is not described in bit mask format
+ ASSERT_EQ(pcm_params_get_mask(params, PCM_PARAM_SAMPLE_BITS), nullptr);
+
+ // test to get mask out of pcm_param enum
+ ASSERT_EQ(pcm_params_get_mask(params, static_cast<pcm_param>(100)), nullptr);
+
+ const pcm_mask *mask = pcm_params_get_mask(params, PCM_PARAM_ACCESS);
+ ASSERT_NE(mask, nullptr);
+
+ pcm_params_free(params);
+}
+
+TEST(PcmParamsTest, GetParamsInterval) {
+ // test to get interval with null params
+ ASSERT_EQ(pcm_params_get_min(nullptr, PCM_PARAM_SAMPLE_BITS), 0);
+ ASSERT_EQ(pcm_params_get_max(nullptr, PCM_PARAM_SAMPLE_BITS), 0);
+
+ // assume that card 0, device 0 is always available.
+ pcm_params *params = pcm_params_get(0, 0, PCM_OUT);
+ ASSERT_NE(params, nullptr);
+
+ // test to get param which is not described in interval format
+ ASSERT_EQ(pcm_params_get_min(params, PCM_PARAM_ACCESS), 0);
+ ASSERT_EQ(pcm_params_get_max(params, PCM_PARAM_ACCESS), 0);
+
+ // test to get interval out of pcm_param enum
+ ASSERT_EQ(pcm_params_get_min(params, static_cast<pcm_param>(100)), 0);
+ ASSERT_EQ(pcm_params_get_max(params, static_cast<pcm_param>(100)), 0);
+
+ pcm_params_free(params);
+}
+
+TEST(PcmParamsTest, ParamsToString) {
+ // assume that card 0, device 0 is always available.
+ pcm_params *params = pcm_params_get(0, 0, PCM_OUT);
+ ASSERT_NE(params, nullptr);
+
+ char long_string[1024] = { 0 };
+ int count = pcm_params_to_string(params, long_string, sizeof(long_string));
+ ASSERT_LE(static_cast<size_t>(count), sizeof(long_string));
+ ASSERT_GT(static_cast<size_t>(count), 0);
+
+ char short_string[1] = { 0 };
+ count = pcm_params_to_string(params, short_string, sizeof(short_string));
+ ASSERT_GT(static_cast<size_t>(count), sizeof(short_string));
+
+ int proper_string_len = count;
+ int proper_string_size = proper_string_len + 1;
+ auto proper_string = std::make_unique<char[]>(proper_string_size);
+ count = pcm_params_to_string(params, proper_string.get(), proper_string_size);
+ ASSERT_GT(static_cast<size_t>(count), 0);
+ ASSERT_EQ(static_cast<size_t>(count), proper_string_len);
+ ASSERT_EQ(std::strlen(proper_string.get()), proper_string_len);
+ pcm_params_free(params);
+}
+
+TEST(PcmParamsTest, GetPlaybackDeviceParams) {
+ pcm_params *params = pcm_params_get(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT);
+ ASSERT_NE(params, nullptr);
+
+ const pcm_mask *access_mask = pcm_params_get_mask(params, PCM_PARAM_ACCESS);
+ ASSERT_NE(access_mask, nullptr);
+ ASSERT_NE(OrAllBits(access_mask), 0);
+
+ const pcm_mask *format_mask = pcm_params_get_mask(params, PCM_PARAM_FORMAT);
+ ASSERT_NE(format_mask, nullptr);
+ ASSERT_NE(OrAllBits(format_mask), 0);
+
+ const pcm_mask *subformat_mask = pcm_params_get_mask(params, PCM_PARAM_SUBFORMAT);
+ ASSERT_NE(subformat_mask, nullptr);
+ ASSERT_NE(OrAllBits(subformat_mask), 0);
+
+ unsigned int sample_bits_min = pcm_params_get_min(params, PCM_PARAM_SAMPLE_BITS);
+ unsigned int sample_bits_max = pcm_params_get_max(params, PCM_PARAM_SAMPLE_BITS);
+ std::cout << "sample_bits: " << sample_bits_min << " - " << sample_bits_max << std::endl;
+ ASSERT_GT(sample_bits_min, 0);
+ ASSERT_GT(sample_bits_max, 0);
+
+ unsigned int frame_bits_min = pcm_params_get_min(params, PCM_PARAM_FRAME_BITS);
+ unsigned int frame_bits_max = pcm_params_get_max(params, PCM_PARAM_FRAME_BITS);
+ std::cout << "frame_bits: " << frame_bits_min << " - " << frame_bits_max << std::endl;
+ ASSERT_GT(frame_bits_min, 0);
+ ASSERT_GT(frame_bits_max, 0);
+
+ unsigned int channels_min = pcm_params_get_min(params, PCM_PARAM_CHANNELS);
+ unsigned int channels_max = pcm_params_get_max(params, PCM_PARAM_CHANNELS);
+ std::cout << "channels: " << channels_min << " - " << channels_max << std::endl;
+ ASSERT_GT(channels_min, 0);
+ ASSERT_GT(channels_max, 0);
+
+ unsigned int sampling_rate_min = pcm_params_get_min(params, PCM_PARAM_RATE);
+ unsigned int sampling_rate_max = pcm_params_get_max(params, PCM_PARAM_RATE);
+ std::cout << "sampling_rate: " << sampling_rate_min << " - " << sampling_rate_max << std::endl;
+ ASSERT_GT(sampling_rate_min, 0);
+ ASSERT_GT(sampling_rate_max, 0);
+
+ unsigned int period_time_min = pcm_params_get_min(params, PCM_PARAM_PERIOD_TIME);
+ unsigned int period_time_max = pcm_params_get_max(params, PCM_PARAM_PERIOD_TIME);
+ std::cout << "period_time: " << period_time_min << " - " << period_time_max << std::endl;
+ ASSERT_GT(period_time_min, 0);
+ ASSERT_GT(period_time_max, 0);
+
+ unsigned int period_size_min = pcm_params_get_min(params, PCM_PARAM_PERIOD_SIZE);
+ unsigned int period_size_max = pcm_params_get_max(params, PCM_PARAM_PERIOD_SIZE);
+ std::cout << "period_size: " << period_size_min << " - " << period_size_max << std::endl;
+ ASSERT_GT(period_size_min, 0);
+ ASSERT_GT(period_size_max, 0);
+
+ unsigned int period_bytes_min = pcm_params_get_min(params, PCM_PARAM_PERIOD_BYTES);
+ unsigned int period_bytes_max = pcm_params_get_max(params, PCM_PARAM_PERIOD_BYTES);
+ std::cout << "period_bytes: " << period_bytes_min << " - " << period_bytes_max << std::endl;
+ ASSERT_GT(period_bytes_min, 0);
+ ASSERT_GT(period_bytes_max, 0);
+
+ unsigned int period_count_min = pcm_params_get_min(params, PCM_PARAM_PERIODS);
+ unsigned int period_count_max = pcm_params_get_max(params, PCM_PARAM_PERIODS);
+ std::cout << "period_count: " << period_count_min << " - " << period_count_max << std::endl;
+ ASSERT_GT(period_count_min, 0);
+ ASSERT_GT(period_count_max, 0);
+
+ unsigned int buffer_time_min = pcm_params_get_min(params, PCM_PARAM_BUFFER_TIME);
+ unsigned int buffer_time_max = pcm_params_get_max(params, PCM_PARAM_BUFFER_TIME);
+ std::cout << "buffer_time: " << buffer_time_min << " - " << buffer_time_max << std::endl;
+ ASSERT_GT(buffer_time_min, 0);
+ ASSERT_GT(buffer_time_max, 0);
+
+ unsigned int buffer_size_min = pcm_params_get_min(params, PCM_PARAM_BUFFER_SIZE);
+ unsigned int buffer_size_max = pcm_params_get_max(params, PCM_PARAM_BUFFER_SIZE);
+ std::cout << "buffer_size: " << buffer_size_min << " - " << buffer_size_max << std::endl;
+ ASSERT_GT(buffer_size_min, 0);
+ ASSERT_GT(buffer_size_max, 0);
+
+ unsigned int buffer_bytes_min = pcm_params_get_min(params, PCM_PARAM_BUFFER_BYTES);
+ unsigned int buffer_bytes_max = pcm_params_get_max(params, PCM_PARAM_BUFFER_BYTES);
+ std::cout << "buffer_bytes: " << buffer_bytes_min << " - " << buffer_bytes_max << std::endl;
+ ASSERT_GT(buffer_bytes_min, 0);
+ ASSERT_GT(buffer_bytes_max, 0);
+
+ unsigned int tick_in_us_min = pcm_params_get_min(params, PCM_PARAM_TICK_TIME);
+ unsigned int tick_in_us_max = pcm_params_get_max(params, PCM_PARAM_TICK_TIME);
+ ASSERT_GT(tick_in_us_max, 0);
+ std::cout << "tick_in_us: " << tick_in_us_min << " - " << tick_in_us_max << std::endl;
+
+ pcm_params_free(params);
+}
+
+} // namespace testing
+} // namespace tinyalsa
diff --git a/tests/src/pcm_test.cc b/tests/src/pcm_test.cc
new file mode 100644
index 0000000..2668350
--- /dev/null
+++ b/tests/src/pcm_test.cc
@@ -0,0 +1,103 @@
+/* pcm_out_test.c
+**
+** Copyright 2020, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+
+#include <string>
+#include <iostream>
+
+#include <gtest/gtest.h>
+
+#include "tinyalsa/pcm.h"
+
+namespace tinyalsa {
+namespace testing {
+
+TEST(PcmTest, FormatToBits) {
+ // FIXME: Should we return 16 bits for INVALID?
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_INVALID), 16);
+
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S16_LE), 16);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S32_LE), 32);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S8), 8);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S24_LE), 32);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S24_3LE), 24);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S16_BE), 16);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S24_BE), 32);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S24_3BE), 24);
+ ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S32_BE), 32);
+}
+
+TEST(PcmTest, OpenAndCloseOutPcm) {
+ static constexpr unsigned int kDefaultChannels = 2;
+ static constexpr unsigned int kDefaultSamplingRate = 48000;
+ static constexpr unsigned int kDefaultPeriodSize = 1024;
+ static constexpr unsigned int kDefaultPeriodCount = 3;
+ static constexpr pcm_config kDefaultConfig = {
+ .channels = kDefaultChannels,
+ .rate = kDefaultSamplingRate,
+ .period_size = kDefaultPeriodSize,
+ .period_count = kDefaultPeriodCount,
+ .format = PCM_FORMAT_S16_LE,
+ .start_threshold = kDefaultPeriodSize,
+ .stop_threshold = kDefaultPeriodSize * kDefaultPeriodCount,
+ .silence_threshold = 0,
+ .silence_size = 0,
+ };
+
+ pcm *pcm_object = pcm_open(1000, 1000, PCM_OUT, &kDefaultConfig);
+ ASSERT_FALSE(pcm_is_ready(pcm_object));
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+
+ // assume card 0, device 0 is always available
+ pcm_object = pcm_open(0, 0, PCM_OUT, &kDefaultConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+
+ pcm_object = pcm_open(0, 0, PCM_OUT | PCM_MMAP, &kDefaultConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+
+ pcm_object = pcm_open(0, 0, PCM_OUT | PCM_MMAP | PCM_NOIRQ, &kDefaultConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+
+ pcm_object = pcm_open(0, 0, PCM_OUT | PCM_NONBLOCK, &kDefaultConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+
+ pcm_object = pcm_open(0, 0, PCM_OUT | PCM_MONOTONIC, &kDefaultConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+
+ std::string name = "hw:0,0";
+ pcm_object = pcm_open_by_name(name.c_str(), PCM_OUT, &kDefaultConfig);
+ ASSERT_TRUE(pcm_is_ready(pcm_object));
+ ASSERT_EQ(pcm_close(pcm_object), 0);
+}
+
+} // namespace testing
+} // namespace tinyalsa
diff --git a/utils/Makefile b/utils/Makefile
new file mode 100644
index 0000000..f733c39
--- /dev/null
+++ b/utils/Makefile
@@ -0,0 +1,59 @@
+DESTDIR ?=
+PREFIX ?= /usr/local
+BINDIR ?= $(PREFIX)/bin
+MANDIR ?= $(PREFIX)/man
+
+CROSS_COMPILE ?=
+CC = $(CROSS_COMPILE)gcc
+
+CFLAGS += -Wall -Wextra -Werror -Wfatal-errors
+CFLAGS += -I ../include
+CFLAGS += -fPIC
+CFLAGS += -O2
+
+LDFLAGS += -L ../src
+LDFLAGS += -pie
+
+VPATH = ../src:../include/tinyalsa
+
+.PHONY: all
+all: -ltinyalsa tinyplay tinycap tinymix tinypcminfo
+
+tinyplay tinycap tinypcminfo tinymix: LDLIBS+=-ldl
+
+tinyplay: tinyplay.o libtinyalsa.a
+
+tinyplay.o: tinyplay.c pcm.h mixer.h asoundlib.h optparse.h
+
+tinycap: tinycap.o libtinyalsa.a
+
+tinycap.o: tinycap.c pcm.h mixer.h asoundlib.h optparse.h
+
+tinymix: tinymix.o libtinyalsa.a
+
+tinymix.o: tinymix.c pcm.h mixer.h asoundlib.h optparse.h
+
+tinypcminfo: tinypcminfo.o libtinyalsa.a
+
+tinypcminfo.o: tinypcminfo.c pcm.h mixer.h asoundlib.h optparse.h
+
+.PHONY: clean
+clean:
+ $(RM) tinyplay tinyplay.o
+ $(RM) tinycap tinycap.o
+ $(RM) tinymix tinymix.o
+ $(RM) tinypcminfo tinypcminfo.o
+
+.PHONY: install
+install: tinyplay tinycap tinymix tinypcminfo
+ install -d $(DESTDIR)$(BINDIR)
+ install tinyplay $(DESTDIR)$(BINDIR)/
+ install tinycap $(DESTDIR)$(BINDIR)/
+ install tinymix $(DESTDIR)$(BINDIR)/
+ install tinypcminfo $(DESTDIR)$(BINDIR)/
+ install -d $(DESTDIR)$(MANDIR)/man1
+ install tinyplay.1 $(DESTDIR)$(MANDIR)/man1/
+ install tinycap.1 $(DESTDIR)$(MANDIR)/man1/
+ install tinymix.1 $(DESTDIR)$(MANDIR)/man1/
+ install tinypcminfo.1 $(DESTDIR)$(MANDIR)/man1/
+
diff --git a/utils/meson.build b/utils/meson.build
new file mode 100644
index 0000000..934402b
--- /dev/null
+++ b/utils/meson.build
@@ -0,0 +1,9 @@
+utils = ['tinyplay', 'tinycap', 'tinymix', 'tinypcminfo']
+
+foreach util : utils
+ executable(util, '@0@.c'.format(util),
+ include_directories: tinyalsa_includes,
+ link_with: tinyalsa,
+ install: true)
+ install_man('@0@.1'.format(util))
+endforeach
diff --git a/utils/optparse.h b/utils/optparse.h
new file mode 100644
index 0000000..3a577a7
--- /dev/null
+++ b/utils/optparse.h
@@ -0,0 +1,403 @@
+/* Optparse --- portable, reentrant, embeddable, getopt-like option parser
+ *
+ * This is free and unencumbered software released into the public domain.
+ *
+ * To get the implementation, define OPTPARSE_IMPLEMENTATION.
+ * Optionally define OPTPARSE_API to control the API's visibility
+ * and/or linkage (static, __attribute__, __declspec).
+ *
+ * The POSIX getopt() option parser has three fatal flaws. These flaws
+ * are solved by Optparse.
+ *
+ * 1) Parser state is stored entirely in global variables, some of
+ * which are static and inaccessible. This means only one thread can
+ * use getopt(). It also means it's not possible to recursively parse
+ * nested sub-arguments while in the middle of argument parsing.
+ * Optparse fixes this by storing all state on a local struct.
+ *
+ * 2) The POSIX standard provides no way to properly reset the parser.
+ * This means for portable code that getopt() is only good for one
+ * run, over one argv with one option string. It also means subcommand
+ * options cannot be processed with getopt(). Most implementations
+ * provide a method to reset the parser, but it's not portable.
+ * Optparse provides an optparse_arg() function for stepping over
+ * subcommands and continuing parsing of options with another option
+ * string. The Optparse struct itself can be passed around to
+ * subcommand handlers for additional subcommand option parsing. A
+ * full reset can be achieved by with an additional optparse_init().
+ *
+ * 3) Error messages are printed to stderr. This can be disabled with
+ * opterr, but the messages themselves are still inaccessible.
+ * Optparse solves this by writing an error message in its errmsg
+ * field. The downside to Optparse is that this error message will
+ * always be in English rather than the current locale.
+ *
+ * Optparse should be familiar with anyone accustomed to getopt(), and
+ * it could be a nearly drop-in replacement. The option string is the
+ * same and the fields have the same names as the getopt() global
+ * variables (optarg, optind, optopt).
+ *
+ * Optparse also supports GNU-style long options with optparse_long().
+ * The interface is slightly different and simpler than getopt_long().
+ *
+ * By default, argv is permuted as it is parsed, moving non-option
+ * arguments to the end. This can be disabled by setting the `permute`
+ * field to 0 after initialization.
+ */
+#ifndef OPTPARSE_H
+#define OPTPARSE_H
+
+#ifndef OPTPARSE_API
+# define OPTPARSE_API
+#endif
+
+struct optparse {
+ char **argv;
+ int permute;
+ int optind;
+ int optopt;
+ char *optarg;
+ char errmsg[64];
+ int subopt;
+};
+
+enum optparse_argtype {
+ OPTPARSE_NONE,
+ OPTPARSE_REQUIRED,
+ OPTPARSE_OPTIONAL
+};
+
+struct optparse_long {
+ const char *longname;
+ int shortname;
+ enum optparse_argtype argtype;
+};
+
+/**
+ * Initializes the parser state.
+ */
+OPTPARSE_API
+void optparse_init(struct optparse *options, char **argv);
+
+/**
+ * Read the next option in the argv array.
+ * @param optstring a getopt()-formatted option string.
+ * @return the next option character, -1 for done, or '?' for error
+ *
+ * Just like getopt(), a character followed by no colons means no
+ * argument. One colon means the option has a required argument. Two
+ * colons means the option takes an optional argument.
+ */
+OPTPARSE_API
+int optparse(struct optparse *options, const char *optstring);
+
+/**
+ * Handles GNU-style long options in addition to getopt() options.
+ * This works a lot like GNU's getopt_long(). The last option in
+ * longopts must be all zeros, marking the end of the array. The
+ * longindex argument may be NULL.
+ */
+OPTPARSE_API
+int optparse_long(struct optparse *options,
+ const struct optparse_long *longopts,
+ int *longindex);
+
+/**
+ * Used for stepping over non-option arguments.
+ * @return the next non-option argument, or NULL for no more arguments
+ *
+ * Argument parsing can continue with optparse() after using this
+ * function. That would be used to parse the options for the
+ * subcommand returned by optparse_arg(). This function allows you to
+ * ignore the value of optind.
+ */
+OPTPARSE_API
+char *optparse_arg(struct optparse *options);
+
+/* Implementation */
+#ifdef OPTPARSE_IMPLEMENTATION
+
+#define OPTPARSE_MSG_INVALID "invalid option"
+#define OPTPARSE_MSG_MISSING "option requires an argument"
+#define OPTPARSE_MSG_TOOMANY "option takes no arguments"
+
+static int
+optparse_error(struct optparse *options, const char *msg, const char *data)
+{
+ unsigned p = 0;
+ const char *sep = " -- '";
+ while (*msg)
+ options->errmsg[p++] = *msg++;
+ while (*sep)
+ options->errmsg[p++] = *sep++;
+ while (p < sizeof(options->errmsg) - 2 && *data)
+ options->errmsg[p++] = *data++;
+ options->errmsg[p++] = '\'';
+ options->errmsg[p++] = '\0';
+ return '?';
+}
+
+OPTPARSE_API
+void
+optparse_init(struct optparse *options, char **argv)
+{
+ options->argv = argv;
+ options->permute = 1;
+ options->optind = 1;
+ options->subopt = 0;
+ options->optarg = 0;
+ options->errmsg[0] = '\0';
+}
+
+static int
+optparse_is_dashdash(const char *arg)
+{
+ return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0';
+}
+
+static int
+optparse_is_shortopt(const char *arg)
+{
+ return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0';
+}
+
+static int
+optparse_is_longopt(const char *arg)
+{
+ return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0';
+}
+
+static void
+optparse_permute(struct optparse *options, int index)
+{
+ char *nonoption = options->argv[index];
+ int i;
+ for (i = index; i < options->optind - 1; i++)
+ options->argv[i] = options->argv[i + 1];
+ options->argv[options->optind - 1] = nonoption;
+}
+
+static int
+optparse_argtype(const char *optstring, char c)
+{
+ int count = OPTPARSE_NONE;
+ if (c == ':')
+ return -1;
+ for (; *optstring && c != *optstring; optstring++);
+ if (!*optstring)
+ return -1;
+ if (optstring[1] == ':')
+ count += optstring[2] == ':' ? 2 : 1;
+ return count;
+}
+
+OPTPARSE_API
+int
+optparse(struct optparse *options, const char *optstring)
+{
+ int type;
+ char *next;
+ char *option = options->argv[options->optind];
+ options->errmsg[0] = '\0';
+ options->optopt = 0;
+ options->optarg = 0;
+ if (option == 0) {
+ return -1;
+ } else if (optparse_is_dashdash(option)) {
+ options->optind++; /* consume "--" */
+ return -1;
+ } else if (!optparse_is_shortopt(option)) {
+ if (options->permute) {
+ int index = options->optind++;
+ int r = optparse(options, optstring);
+ optparse_permute(options, index);
+ options->optind--;
+ return r;
+ } else {
+ return -1;
+ }
+ }
+ option += options->subopt + 1;
+ options->optopt = option[0];
+ type = optparse_argtype(optstring, option[0]);
+ next = options->argv[options->optind + 1];
+ switch (type) {
+ case -1: {
+ char str[2] = {0, 0};
+ str[0] = option[0];
+ options->optind++;
+ return optparse_error(options, OPTPARSE_MSG_INVALID, str);
+ }
+ case OPTPARSE_NONE:
+ if (option[1]) {
+ options->subopt++;
+ } else {
+ options->subopt = 0;
+ options->optind++;
+ }
+ return option[0];
+ case OPTPARSE_REQUIRED:
+ options->subopt = 0;
+ options->optind++;
+ if (option[1]) {
+ options->optarg = option + 1;
+ } else if (next != 0) {
+ options->optarg = next;
+ options->optind++;
+ } else {
+ char str[2] = {0, 0};
+ str[0] = option[0];
+ options->optarg = 0;
+ return optparse_error(options, OPTPARSE_MSG_MISSING, str);
+ }
+ return option[0];
+ case OPTPARSE_OPTIONAL:
+ options->subopt = 0;
+ options->optind++;
+ if (option[1])
+ options->optarg = option + 1;
+ else
+ options->optarg = 0;
+ return option[0];
+ }
+ return 0;
+}
+
+OPTPARSE_API
+char *
+optparse_arg(struct optparse *options)
+{
+ char *option = options->argv[options->optind];
+ options->subopt = 0;
+ if (option != 0)
+ options->optind++;
+ return option;
+}
+
+static int
+optparse_longopts_end(const struct optparse_long *longopts, int i)
+{
+ return !longopts[i].longname && !longopts[i].shortname;
+}
+
+static void
+optparse_from_long(const struct optparse_long *longopts, char *optstring)
+{
+ char *p = optstring;
+ int i;
+ for (i = 0; !optparse_longopts_end(longopts, i); i++) {
+ if (longopts[i].shortname) {
+ int a;
+ *p++ = longopts[i].shortname;
+ for (a = 0; a < (int)longopts[i].argtype; a++)
+ *p++ = ':';
+ }
+ }
+ *p = '\0';
+}
+
+/* Unlike strcmp(), handles options containing "=". */
+static int
+optparse_longopts_match(const char *longname, const char *option)
+{
+ const char *a = option, *n = longname;
+ if (longname == 0)
+ return 0;
+ for (; *a && *n && *a != '='; a++, n++)
+ if (*a != *n)
+ return 0;
+ return *n == '\0' && (*a == '\0' || *a == '=');
+}
+
+/* Return the part after "=", or NULL. */
+static char *
+optparse_longopts_arg(char *option)
+{
+ for (; *option && *option != '='; option++);
+ if (*option == '=')
+ return option + 1;
+ else
+ return 0;
+}
+
+static int
+optparse_long_fallback(struct optparse *options,
+ const struct optparse_long *longopts,
+ int *longindex)
+{
+ int result;
+ char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */
+ optparse_from_long(longopts, optstring);
+ result = optparse(options, optstring);
+ if (longindex != 0) {
+ *longindex = -1;
+ if (result != -1) {
+ int i;
+ for (i = 0; !optparse_longopts_end(longopts, i); i++)
+ if (longopts[i].shortname == options->optopt)
+ *longindex = i;
+ }
+ }
+ return result;
+}
+
+OPTPARSE_API
+int
+optparse_long(struct optparse *options,
+ const struct optparse_long *longopts,
+ int *longindex)
+{
+ int i;
+ char *option = options->argv[options->optind];
+ if (option == 0) {
+ return -1;
+ } else if (optparse_is_dashdash(option)) {
+ options->optind++; /* consume "--" */
+ return -1;
+ } else if (optparse_is_shortopt(option)) {
+ return optparse_long_fallback(options, longopts, longindex);
+ } else if (!optparse_is_longopt(option)) {
+ if (options->permute) {
+ int index = options->optind++;
+ int r = optparse_long(options, longopts, longindex);
+ optparse_permute(options, index);
+ options->optind--;
+ return r;
+ } else {
+ return -1;
+ }
+ }
+
+ /* Parse as long option. */
+ options->errmsg[0] = '\0';
+ options->optopt = 0;
+ options->optarg = 0;
+ option += 2; /* skip "--" */
+ options->optind++;
+ for (i = 0; !optparse_longopts_end(longopts, i); i++) {
+ const char *name = longopts[i].longname;
+ if (optparse_longopts_match(name, option)) {
+ char *arg;
+ if (longindex)
+ *longindex = i;
+ options->optopt = longopts[i].shortname;
+ arg = optparse_longopts_arg(option);
+ if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) {
+ return optparse_error(options, OPTPARSE_MSG_TOOMANY, name);
+ } if (arg != 0) {
+ options->optarg = arg;
+ } else if (longopts[i].argtype == OPTPARSE_REQUIRED) {
+ options->optarg = options->argv[options->optind];
+ if (options->optarg == 0)
+ return optparse_error(options, OPTPARSE_MSG_MISSING, name);
+ else
+ options->optind++;
+ }
+ return options->optopt;
+ }
+ }
+ return optparse_error(options, OPTPARSE_MSG_INVALID, option);
+}
+
+#endif /* OPTPARSE_IMPLEMENTATION */
+#endif /* OPTPARSE_H */
diff --git a/utils/tinycap.1 b/utils/tinycap.1
new file mode 100644
index 0000000..ad60a2e
--- /dev/null
+++ b/utils/tinycap.1
@@ -0,0 +1,87 @@
+.TH TINYCAP 1 "October 2, 2016" "tinycap" "TinyALSA"
+
+.SH NAME
+tinycap \- captures audio from an audio device
+
+.SH SYNOPSIS
+.B tinycap\fR [ \fIfile\fR ] [ \fIoptions\fR ]
+
+.SH Description
+
+\fBtinycap\fR can record audio from an audio device to a wav file or standard output (as raw samples).
+Options can be used to specify various hardware parameters to open the PCM with.
+
+.SH OPTIONS
+
+.TP
+\fB\-D\fR \fIcard\fR
+Card number of the PCM.
+The default is 0.
+
+.TP
+\fB\-d\fR \fIdevice\fR
+Device number of the PCM.
+The default is 0.
+
+.TP
+\fB\-c\fR \fIchannels\fR
+Number of channels the PCM will have.
+The default is 2.
+
+.TP
+\fB\-r\fR \fIrate\fR
+Number of frames per second of the PCM.
+The default is 48000.
+
+.TP
+\fB\-b\fR \fIbits\fR
+Number of bits per sample the PCM will have.
+The default is 32.
+
+.TP
+\fB\-p\fR \fIperiod_size\fR
+Number of frames in a period.
+The default is 1024.
+
+.TP
+\fB\-n\fR \fIperiods\fR
+Number of periods the PCM will have.
+The default is 4.
+
+.TP
+\fB\-t\fR \fIseconds\fR
+Number of seconds to record audio.
+
+.SH SIGNALS
+
+When capturing audio, SIGINT will stop the recording and close the file.
+
+.SH EXAMPLES
+
+.TP
+\fBtinycap output.wav\fR
+Records a file called output.wav until an interrupt signal is caught.
+
+.TP
+\fBtinycap output.wav -D 1 -t 2
+Records a file called output.wav from card 1 for two seconds or until an interrupt signal is caught.
+
+.TP
+\fBtinycap -- -t 3
+Records to standard output for three seconds or until an interrupt signal is caught.
+
+.SH BUGS
+
+Please report bugs to https://github.com/tinyalsa/tinyalsa/issues.
+
+.SH SEE ALSO
+
+.BR tinyplay(1),
+.BR tinymix(1),
+.BR tinypcminfo(1)
+
+.SH AUTHORS
+Simon Wilson
+.P
+For a complete list of authors, visit the project page at https://github.com/tinyalsa/tinyalsa.
+
diff --git a/utils/tinycap.c b/utils/tinycap.c
new file mode 100644
index 0000000..7d4b8a4
--- /dev/null
+++ b/utils/tinycap.c
@@ -0,0 +1,267 @@
+/* tinycap.c
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+
+#include <tinyalsa/asoundlib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <signal.h>
+#include <string.h>
+#include <limits.h>
+
+#define OPTPARSE_IMPLEMENTATION
+#include "optparse.h"
+
+#define ID_RIFF 0x46464952
+#define ID_WAVE 0x45564157
+#define ID_FMT 0x20746d66
+#define ID_DATA 0x61746164
+
+#define FORMAT_PCM 1
+
+struct wav_header {
+ uint32_t riff_id;
+ uint32_t riff_sz;
+ uint32_t riff_fmt;
+ uint32_t fmt_id;
+ uint32_t fmt_sz;
+ uint16_t audio_format;
+ uint16_t num_channels;
+ uint32_t sample_rate;
+ uint32_t byte_rate;
+ uint16_t block_align;
+ uint16_t bits_per_sample;
+ uint32_t data_id;
+ uint32_t data_sz;
+};
+
+int capturing = 1;
+int prinfo = 1;
+
+unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device,
+ unsigned int channels, unsigned int rate,
+ enum pcm_format format, unsigned int period_size,
+ unsigned int period_count, unsigned int capture_time);
+
+void sigint_handler(int sig)
+{
+ if (sig == SIGINT){
+ capturing = 0;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ FILE *file;
+ struct wav_header header;
+ unsigned int card = 0;
+ unsigned int device = 0;
+ unsigned int channels = 2;
+ unsigned int rate = 48000;
+ unsigned int bits = 16;
+ unsigned int frames;
+ unsigned int period_size = 1024;
+ unsigned int period_count = 4;
+ unsigned int capture_time = UINT_MAX;
+ enum pcm_format format;
+ int no_header = 0, c;
+ struct optparse opts;
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s {file.wav | --} [-D card] [-d device] [-c channels] "
+ "[-r rate] [-b bits] [-p period_size] [-n n_periods] [-t time_in_seconds]\n\n"
+ "Use -- for filename to send raw PCM to stdout\n", argv[0]);
+ return 1;
+ }
+
+ if (strcmp(argv[1],"--") == 0) {
+ file = stdout;
+ prinfo = 0;
+ no_header = 1;
+ } else {
+ file = fopen(argv[1], "wb");
+ if (!file) {
+ fprintf(stderr, "Unable to create file '%s'\n", argv[1]);
+ return 1;
+ }
+ }
+
+ /* parse command line arguments */
+ optparse_init(&opts, argv + 1);
+ while ((c = optparse(&opts, "D:d:c:r:b:p:n:t:")) != -1) {
+ switch (c) {
+ case 'd':
+ device = atoi(opts.optarg);
+ break;
+ case 'c':
+ channels = atoi(opts.optarg);
+ break;
+ case 'r':
+ rate = atoi(opts.optarg);
+ break;
+ case 'b':
+ bits = atoi(opts.optarg);
+ break;
+ case 'D':
+ card = atoi(opts.optarg);
+ break;
+ case 'p':
+ period_size = atoi(opts.optarg);
+ break;
+ case 'n':
+ period_count = atoi(opts.optarg);
+ break;
+ case 't':
+ capture_time = atoi(opts.optarg);
+ break;
+ case '?':
+ fprintf(stderr, "%s\n", opts.errmsg);
+ return EXIT_FAILURE;
+ }
+ }
+
+ header.riff_id = ID_RIFF;
+ header.riff_sz = 0;
+ header.riff_fmt = ID_WAVE;
+ header.fmt_id = ID_FMT;
+ header.fmt_sz = 16;
+ header.audio_format = FORMAT_PCM;
+ header.num_channels = channels;
+ header.sample_rate = rate;
+
+ switch (bits) {
+ case 32:
+ format = PCM_FORMAT_S32_LE;
+ break;
+ case 24:
+ format = PCM_FORMAT_S24_LE;
+ break;
+ case 16:
+ format = PCM_FORMAT_S16_LE;
+ break;
+ default:
+ fprintf(stderr, "%u bits is not supported.\n", bits);
+ fclose(file);
+ return 1;
+ }
+
+ header.bits_per_sample = pcm_format_to_bits(format);
+ header.byte_rate = (header.bits_per_sample / 8) * channels * rate;
+ header.block_align = channels * (header.bits_per_sample / 8);
+ header.data_id = ID_DATA;
+
+ /* leave enough room for header */
+ if (!no_header) {
+ fseek(file, sizeof(struct wav_header), SEEK_SET);
+ }
+
+ /* install signal handler and begin capturing */
+ signal(SIGINT, sigint_handler);
+ frames = capture_sample(file, card, device, header.num_channels,
+ header.sample_rate, format,
+ period_size, period_count, capture_time);
+ if (prinfo) {
+ printf("Captured %u frames\n", frames);
+ }
+
+ /* write header now all information is known */
+ if (!no_header) {
+ header.data_sz = frames * header.block_align;
+ header.riff_sz = header.data_sz + sizeof(header) - 8;
+ fseek(file, 0, SEEK_SET);
+ fwrite(&header, sizeof(struct wav_header), 1, file);
+ }
+
+ fclose(file);
+
+ return 0;
+}
+
+unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device,
+ unsigned int channels, unsigned int rate,
+ enum pcm_format format, unsigned int period_size,
+ unsigned int period_count, unsigned int capture_time)
+{
+ struct pcm_config config;
+ struct pcm *pcm;
+ char *buffer;
+ unsigned int size;
+ unsigned int frames_read;
+ unsigned int total_frames_read;
+ unsigned int bytes_per_frame;
+
+ memset(&config, 0, sizeof(config));
+ config.channels = channels;
+ config.rate = rate;
+ config.period_size = period_size;
+ config.period_count = period_count;
+ config.format = format;
+ config.start_threshold = 0;
+ config.stop_threshold = 0;
+ config.silence_threshold = 0;
+
+ pcm = pcm_open(card, device, PCM_IN, &config);
+ if (!pcm || !pcm_is_ready(pcm)) {
+ fprintf(stderr, "Unable to open PCM device (%s)\n",
+ pcm_get_error(pcm));
+ return 0;
+ }
+
+ size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
+ buffer = malloc(size);
+ if (!buffer) {
+ fprintf(stderr, "Unable to allocate %u bytes\n", size);
+ pcm_close(pcm);
+ return 0;
+ }
+
+ if (prinfo) {
+ printf("Capturing sample: %u ch, %u hz, %u bit\n", channels, rate,
+ pcm_format_to_bits(format));
+ }
+
+ bytes_per_frame = pcm_frames_to_bytes(pcm, 1);
+ total_frames_read = 0;
+ frames_read = 0;
+ while (capturing) {
+ frames_read = pcm_readi(pcm, buffer, pcm_get_buffer_size(pcm));
+ total_frames_read += frames_read;
+ if ((total_frames_read / rate) >= capture_time) {
+ capturing = 0;
+ }
+ if (fwrite(buffer, bytes_per_frame, frames_read, file) != frames_read) {
+ fprintf(stderr,"Error capturing sample\n");
+ break;
+ }
+ }
+
+ free(buffer);
+ pcm_close(pcm);
+ return total_frames_read;
+}
+
diff --git a/utils/tinymix.1 b/utils/tinymix.1
new file mode 100644
index 0000000..e56d67c
--- /dev/null
+++ b/utils/tinymix.1
@@ -0,0 +1,86 @@
+.TH TINYMIX 1 "October 2, 2016" "tinymix" "TinyALSA"
+
+.SH NAME
+tinymix \- view and edit mixer controls for a specified mixer.
+
+.SH SYNOPSIS
+.B tinymix\fR [ \fIoptions\fR ] \fIcommand\fR
+
+.SH Description
+
+\fBtinymix\fR can be used to view and/or edit a list of mixer controls for a specified mixer.
+
+.SH OPTIONS
+
+.TP
+\fB\-D, --card\fR \fIcard\fR
+Card number of the mixer.
+The default is 0.
+
+.TP
+\fB\-h, --help\fR
+Print help contents and exit.
+
+.TP
+\fB\-v, --version\fR
+Print the current version of tinymix and exit.
+
+.SH COMMANDS
+
+.TP
+\fBget <control-id|control-name>\fR
+Prints the value of a specified control
+
+.TP
+\fBset <control-id|control-name> <control-value>\fR
+Sets the value of a specified control
+
+.TP
+\fBcontents\fR
+Prints the contents of all mixer controls.
+
+.TP
+\fBcontrols\fR
+Prints the names and IDs of all mixer controls.
+
+.SH EXAMPLES
+
+.TP
+\fBtinymix controls\fR
+Prints a list of control IDs for the mixer of card 0.
+
+.TP
+\fBtinymix -D 1 controls\fR
+Prints a list of control IDs for the mixer of card 1.
+
+.TP
+\fBtinymix get 0\fR
+Prints information about control 0.
+
+.TP
+\fBtinymix get "Headphone Playback Volume"\fR
+Prints information about a control called "Headphone Playback Volume"\fR
+
+.TP
+\fBtinymix set 0 4\fR
+Sets control 0 to the value of 4.
+
+.TP
+\fBtinymix --card 1 set 2 32
+Sets control 2 of card 1 to the value of 32.
+
+.SH BUGS
+
+Please report bugs to https://github.com/tinyalsa/tinyalsa/issues.
+
+.SH SEE ALSO
+
+.BR tinycap(1),
+.BR tinyplay(1),
+.BR tinypcminfo(1)
+
+.SH AUTHORS
+Simon Wilson
+.P
+For a complete list of authors, visit the project page at https://github.com/tinyalsa/tinyalsa.
+
diff --git a/utils/tinymix.c b/utils/tinymix.c
new file mode 100644
index 0000000..5c0378c
--- /dev/null
+++ b/utils/tinymix.c
@@ -0,0 +1,580 @@
+/* tinymix.c
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+
+#include <tinyalsa/asoundlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+
+#define OPTPARSE_IMPLEMENTATION
+#include "optparse.h"
+
+static void list_controls(struct mixer *mixer, int print_all);
+
+static void print_control_values(struct mixer_ctl *control);
+static void print_control_values_by_name_or_id(struct mixer *mixer, const char *name_or_id);
+
+static int set_values(struct mixer *mixer, const char *control,
+ char **values, unsigned int num_values);
+
+static void print_enum(struct mixer_ctl *ctl);
+
+void usage(void)
+{
+ printf("usage: tinymix [options] <command>\n");
+ printf("options:\n");
+ printf("\t-h, --help : prints this help message and exits\n");
+ printf("\t-v, --version : prints this version of tinymix and exits\n");
+ printf("\t-D, --card NUMBER : specifies the card number of the mixer\n");
+ printf("\n");
+ printf("commands:\n");
+ printf("\tget NAME|ID : prints the values of a control\n");
+ printf("\tset NAME|ID VALUE(S) ... : sets the value of a control\n");
+ printf("\t\tVALUE(S): integers, percents, and relative values\n");
+ printf("\t\t\tIntegers: 0, 100, -100 ...\n");
+ printf("\t\t\tPercents: 0%%, 100%% ...\n");
+ printf("\t\t\tRelative values: 1+, 1-, 1%%+, 2%%+ ...\n");
+ printf("\tcontrols : lists controls of the mixer\n");
+ printf("\tcontents : lists controls of the mixer and their contents\n");
+}
+
+void version(void)
+{
+ printf("tinymix version 2.0 (tinyalsa version %s)\n", TINYALSA_VERSION_STRING);
+}
+
+static int is_command(char *arg) {
+ return strcmp(arg, "get") == 0 || strcmp(arg, "set") == 0 ||
+ strcmp(arg, "controls") == 0 || strcmp(arg, "contents") == 0;
+}
+
+static int find_command_position(int argc, char **argv)
+{
+ for (int i = 0; i < argc; ++i) {
+ if (is_command(argv[i])) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+int main(int argc, char **argv)
+{
+ int card = 0;
+ struct optparse opts;
+ static struct optparse_long long_options[] = {
+ { "card", 'D', OPTPARSE_REQUIRED },
+ { "version", 'v', OPTPARSE_NONE },
+ { "help", 'h', OPTPARSE_NONE },
+ { 0, 0, 0 }
+ };
+
+ // optparse_long may change the order of the argv list. Duplicate one for parsing the options.
+ char **argv_options_list = (char **) calloc(argc + 1, sizeof(char *));
+ if (!argv_options_list) {
+ fprintf(stderr, "Failed to allocate options list\n");
+ return EXIT_FAILURE;
+ }
+
+ for (int i = 0; i < argc; ++i) {
+ argv_options_list[i] = argv[i];
+ }
+
+ optparse_init(&opts, argv_options_list);
+ /* Detect the end of the options. */
+ int c;
+ while ((c = optparse_long(&opts, long_options, NULL)) != -1) {
+ switch (c) {
+ case 'D':
+ card = atoi(opts.optarg);
+ break;
+ case 'h':
+ usage();
+ free(argv_options_list);
+ return EXIT_SUCCESS;
+ case 'v':
+ version();
+ free(argv_options_list);
+ return EXIT_SUCCESS;
+ case '?':
+ default:
+ break;
+ }
+ }
+ free(argv_options_list);
+
+ struct mixer *mixer = mixer_open(card);
+ if (!mixer) {
+ fprintf(stderr, "Failed to open mixer\n");
+ return EXIT_FAILURE;
+ }
+
+ int command_position = find_command_position(argc, argv);
+ if (command_position < 0) {
+ usage();
+ mixer_close(mixer);
+ return EXIT_FAILURE;
+ }
+
+ char *cmd = argv[command_position];
+ if (strcmp(cmd, "get") == 0) {
+ if (command_position + 1 >= argc) {
+ fprintf(stderr, "no control specified\n");
+ mixer_close(mixer);
+ return EXIT_FAILURE;
+ }
+ print_control_values_by_name_or_id(mixer, argv[command_position + 1]);
+ printf("\n");
+ } else if (strcmp(cmd, "set") == 0) {
+ if (command_position + 1 >= argc) {
+ fprintf(stderr, "no control specified\n");
+ mixer_close(mixer);
+ return EXIT_FAILURE;
+ }
+ if (command_position + 2 >= argc) {
+ fprintf(stderr, "no value(s) specified\n");
+ mixer_close(mixer);
+ return EXIT_FAILURE;
+ }
+ int res = set_values(mixer,
+ argv[command_position + 1],
+ &argv[command_position + 2],
+ argc - command_position - 2);
+ if (res != 0) {
+ mixer_close(mixer);
+ return EXIT_FAILURE;
+ }
+ } else if (strcmp(cmd, "controls") == 0) {
+ list_controls(mixer, 0);
+ } else if (strcmp(cmd, "contents") == 0) {
+ list_controls(mixer, 1);
+ } else {
+ fprintf(stderr, "unknown command '%s'\n", cmd);
+ usage();
+ mixer_close(mixer);
+ return EXIT_FAILURE;
+ }
+
+ mixer_close(mixer);
+ return EXIT_SUCCESS;
+}
+
+static int isnumber(const char *str) {
+ char *end;
+
+ if (str == NULL || strlen(str) == 0)
+ return 0;
+
+ strtol(str, &end, 0);
+ return strlen(end) == 0;
+}
+
+static void list_controls(struct mixer *mixer, int print_all)
+{
+ struct mixer_ctl *ctl;
+ const char *name, *type;
+ unsigned int num_ctls, num_values;
+ unsigned int i;
+
+ num_ctls = mixer_get_num_ctls(mixer);
+
+ printf("Number of controls: %u\n", num_ctls);
+
+ if (print_all)
+ printf("ctl\ttype\tnum\t%-40svalue\n", "name");
+ else
+ printf("ctl\ttype\tnum\t%-40s\n", "name");
+
+ for (i = 0; i < num_ctls; i++) {
+ ctl = mixer_get_ctl(mixer, i);
+
+ name = mixer_ctl_get_name(ctl);
+ type = mixer_ctl_get_type_string(ctl);
+ num_values = mixer_ctl_get_num_values(ctl);
+ printf("%u\t%s\t%u\t%-40s", i, type, num_values, name);
+ if (print_all)
+ print_control_values(ctl);
+ printf("\n");
+ }
+}
+
+static void print_enum(struct mixer_ctl *ctl)
+{
+ unsigned int num_enums;
+ unsigned int i;
+ unsigned int value;
+ const char *string;
+
+ num_enums = mixer_ctl_get_num_enums(ctl);
+ value = mixer_ctl_get_value(ctl, 0);
+
+ for (i = 0; i < num_enums; i++) {
+ string = mixer_ctl_get_enum_string(ctl, i);
+ printf("%s%s, ", value == i ? "> " : "", string);
+ }
+}
+
+static void print_control_values_by_name_or_id(struct mixer *mixer, const char *name_or_id)
+{
+ struct mixer_ctl *ctl;
+
+ if (isnumber(name_or_id))
+ ctl = mixer_get_ctl(mixer, atoi(name_or_id));
+ else
+ ctl = mixer_get_ctl_by_name(mixer, name_or_id);
+
+ if (!ctl) {
+ fprintf(stderr, "Invalid mixer control\n");
+ return;
+ }
+
+ print_control_values(ctl);
+}
+
+static void print_control_values(struct mixer_ctl *control)
+{
+ enum mixer_ctl_type type;
+ unsigned int num_values;
+ unsigned int i;
+ int min, max;
+ int ret;
+ char *buf = NULL;
+
+ type = mixer_ctl_get_type(control);
+ num_values = mixer_ctl_get_num_values(control);
+
+ if ((type == MIXER_CTL_TYPE_BYTE) && (num_values > 0)) {
+ buf = calloc(1, num_values);
+ if (buf == NULL) {
+ fprintf(stderr, "Failed to alloc mem for bytes %u\n", num_values);
+ return;
+ }
+
+ ret = mixer_ctl_get_array(control, buf, num_values);
+ if (ret < 0) {
+ fprintf(stderr, "Failed to mixer_ctl_get_array\n");
+ free(buf);
+ return;
+ }
+ }
+
+ for (i = 0; i < num_values; i++) {
+ switch (type)
+ {
+ case MIXER_CTL_TYPE_INT:
+ printf("%d", mixer_ctl_get_value(control, i));
+ break;
+ case MIXER_CTL_TYPE_BOOL:
+ printf("%s", mixer_ctl_get_value(control, i) ? "On" : "Off");
+ break;
+ case MIXER_CTL_TYPE_ENUM:
+ print_enum(control);
+ break;
+ case MIXER_CTL_TYPE_BYTE:
+ printf("%02hhx", buf[i]);
+ break;
+ default:
+ printf("unknown");
+ break;
+ };
+ if ((i + 1) < num_values) {
+ printf(", ");
+ }
+ }
+
+ if (type == MIXER_CTL_TYPE_INT) {
+ min = mixer_ctl_get_range_min(control);
+ max = mixer_ctl_get_range_max(control);
+ printf(" (range %d->%d)", min, max);
+ }
+
+ free(buf);
+}
+
+static void tinymix_set_byte_ctl(struct mixer_ctl *ctl,
+ char **values, unsigned int num_values)
+{
+ int ret;
+ char *buf;
+ char *end;
+ unsigned int i;
+ long n;
+
+ buf = calloc(1, num_values);
+ if (buf == NULL) {
+ fprintf(stderr, "set_byte_ctl: Failed to alloc mem for bytes %u\n", num_values);
+ exit(EXIT_FAILURE);
+ }
+
+ for (i = 0; i < num_values; i++) {
+ errno = 0;
+ n = strtol(values[i], &end, 0);
+ if (*end) {
+ fprintf(stderr, "%s not an integer\n", values[i]);
+ goto fail;
+ }
+ if (errno) {
+ fprintf(stderr, "strtol: %s: %s\n", values[i],
+ strerror(errno));
+ goto fail;
+ }
+ if (n < 0 || n > 0xff) {
+ fprintf(stderr, "%s should be between [0, 0xff]\n",
+ values[i]);
+ goto fail;
+ }
+ buf[i] = n;
+ }
+
+ ret = mixer_ctl_set_array(ctl, buf, num_values);
+ if (ret < 0) {
+ fprintf(stderr, "Failed to set binary control\n");
+ goto fail;
+ }
+
+ free(buf);
+ return;
+
+fail:
+ free(buf);
+ exit(EXIT_FAILURE);
+}
+
+static int is_int(const char *value)
+{
+ return value[0] >= '0' || value[0] <= '9';
+}
+
+struct parsed_int
+{
+ /** Wether or not the integer was valid. */
+ int valid;
+ /** The value of the parsed integer. */
+ int value;
+ /** The number of characters that were parsed. */
+ unsigned int length;
+ /** The number of characters remaining in the string. */
+ unsigned int remaining_length;
+ /** The remaining characters (or suffix) of the integer. */
+ const char* remaining;
+};
+
+static struct parsed_int parse_int(const char* str)
+{
+ struct parsed_int out = {
+ 0 /* valid */,
+ 0 /* value */,
+ 0 /* length */,
+ 0 /* remaining length */,
+ NULL /* remaining characters */
+ };
+
+ size_t length = strlen(str);
+ size_t i = 0;
+ int negative = 0;
+
+ if (i < length && str[i] == '-') {
+ negative = 1;
+ i++;
+ }
+
+ while (i < length) {
+ char c = str[i++];
+
+ if (c < '0' || c > '9') {
+ --i;
+ break;
+ }
+
+ out.value *= 10;
+ out.value += c - '0';
+
+ out.length++;
+ }
+
+ if (negative) {
+ out.value *= -1;
+ }
+
+ out.valid = out.length > 0;
+ out.remaining_length = length - out.length;
+ out.remaining = str + out.length;
+
+ return out;
+}
+
+struct control_value
+{
+ int value;
+ int is_percent;
+ int is_relative;
+};
+
+static struct control_value to_control_value(const char* value_string)
+{
+ struct parsed_int parsed_int = parse_int(value_string);
+
+ struct control_value out = {
+ 0 /* value */,
+ 0 /* is percent */,
+ 0 /* is relative */
+ };
+
+ out.value = parsed_int.value;
+
+ unsigned int i = 0;
+
+ if (parsed_int.remaining[i] == '%') {
+ out.is_percent = 1;
+ i++;
+ }
+
+ if (parsed_int.remaining[i] == '+') {
+ out.is_relative = 1;
+ } else if (parsed_int.remaining[i] == '-') {
+ out.is_relative = 1;
+ out.value *= -1;
+ }
+
+ return out;
+}
+
+static int set_control_value(struct mixer_ctl* ctl, unsigned int i,
+ const struct control_value* value)
+{
+ int next_value = value->value;
+
+ if (value->is_relative) {
+
+ int prev_value = value->is_percent ? mixer_ctl_get_percent(ctl, i)
+ : mixer_ctl_get_value(ctl, i);
+
+ if (prev_value < 0) {
+ return prev_value;
+ }
+
+ next_value += prev_value;
+ }
+
+ return value->is_percent ? mixer_ctl_set_percent(ctl, i, next_value)
+ : mixer_ctl_set_value(ctl, i, next_value);
+}
+
+static int set_control_values(struct mixer_ctl* ctl,
+ char** values,
+ unsigned int num_values)
+{
+ unsigned int num_ctl_values = mixer_ctl_get_num_values(ctl);
+
+ if (num_values == 1) {
+
+ /* Set all values the same */
+ struct control_value value = to_control_value(values[0]);
+
+ for (unsigned int i = 0; i < num_values; i++) {
+ int res = set_control_value(ctl, i, &value);
+ if (res != 0) {
+ fprintf(stderr, "Error: invalid value (%d%s%s)\n", value.value,
+ value.is_relative ? "r" : "", value.is_percent ? "%" : "");
+ return -1;
+ }
+ }
+
+ } else {
+
+ /* Set multiple values */
+ if (num_values > num_ctl_values) {
+ fprintf(stderr,
+ "Error: %u values given, but control only takes %u\n",
+ num_values, num_ctl_values);
+ return -1;
+ }
+
+ for (unsigned int i = 0; i < num_values; i++) {
+
+ struct control_value v = to_control_value(values[i]);
+
+ int res = set_control_value(ctl, i, &v);
+ if (res != 0) {
+ fprintf(stderr, "Error: invalid value (%d%s%s) for index %u\n", v.value,
+ v.is_relative ? "r" : "", v.is_percent ? "%" : "", i);
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int set_values(struct mixer *mixer, const char *control,
+ char **values, unsigned int num_values)
+{
+ struct mixer_ctl *ctl;
+ enum mixer_ctl_type type;
+
+ if (isnumber(control))
+ ctl = mixer_get_ctl(mixer, atoi(control));
+ else
+ ctl = mixer_get_ctl_by_name(mixer, control);
+
+ if (!ctl) {
+ fprintf(stderr, "Invalid mixer control\n");
+ return -1;
+ }
+
+ type = mixer_ctl_get_type(ctl);
+
+ if (type == MIXER_CTL_TYPE_BYTE) {
+ tinymix_set_byte_ctl(ctl, values, num_values);
+ return 0;
+ }
+
+ if (is_int(values[0])) {
+ set_control_values(ctl, values, num_values);
+ } else {
+ if (type == MIXER_CTL_TYPE_ENUM) {
+ if (num_values != 1) {
+ fprintf(stderr, "Enclose strings in quotes and try again\n");
+ return -1;
+ }
+ if (mixer_ctl_set_enum_by_string(ctl, values[0])) {
+ fprintf(stderr, "Error: invalid enum value\n");
+ return -1;
+ }
+ } else {
+ fprintf(stderr, "Error: only enum types can be set with strings\n");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
diff --git a/utils/tinypcminfo.1 b/utils/tinypcminfo.1
new file mode 100644
index 0000000..bcbcf96
--- /dev/null
+++ b/utils/tinypcminfo.1
@@ -0,0 +1,57 @@
+.TH TINYPCMINFO 1 "October 2, 2016" "tinypcminfo" "TinyALSA"
+
+.SH NAME
+tinypcminfo \- prints the hardware parameters of a PCM.
+
+.SH SYNOPSIS
+.B tinypcminfo\fR [ \fIoptions\fR ]
+
+.SH Description
+
+\fBtinypcminfo\fR prints the hardware parameters of a PCM, specified by it's card and device number.
+
+.SH OPTIONS
+
+.TP
+\fB\-D\fR \fIcard\fR
+Card number of the PCM.
+The default is 0.
+
+.TP
+\fB\-d\fR \fIdevice\fR
+Device number of the PCM.
+The default is 0.
+
+.SH EXAMPLES
+
+.TP
+\fBtinypcminfo\fR
+Prints hardware parameters for the PCM of card 0 and device 0.
+
+.TP
+\fBtinypcminfo -D 1
+Prints hardware parameters for the PCM of card 1 and device 0.
+
+.TP
+\fBtinypcminfo -d 1
+Prints hardware parameters for the PCM of card 0 and device 1.
+
+.TP
+\fBtinypcminfo -D 1 -d 1
+Prints hardware parameters for the PCM of card 1 and device 1.
+
+.SH BUGS
+
+Please report bugs to https://github.com/tinyalsa/tinyalsa/issues.
+
+.SH SEE ALSO
+
+.BR tinycap(1),
+.BR tinyplay(1),
+.BR tinymix(1)
+
+.SH AUTHORS
+Simon Wilson
+.P
+For a complete list of authors, visit the project page at https://github.com/tinyalsa/tinyalsa.
+
diff --git a/utils/tinypcminfo.c b/utils/tinypcminfo.c
new file mode 100644
index 0000000..3116b7c
--- /dev/null
+++ b/utils/tinypcminfo.c
@@ -0,0 +1,211 @@
+/* tinypcminfo.c
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+
+#include <tinyalsa/asoundlib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define OPTPARSE_IMPLEMENTATION
+#include "optparse.h"
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
+#endif
+
+/* The format_lookup is in order of SNDRV_PCM_FORMAT_##index and
+ * matches the grouping in sound/asound.h. Note this is not
+ * continuous and has an empty gap from (25 - 30).
+ */
+static const char *format_lookup[] = {
+ /*[0] =*/ "S8",
+ "U8",
+ "S16_LE",
+ "S16_BE",
+ "U16_LE",
+ "U16_BE",
+ "S24_LE",
+ "S24_BE",
+ "U24_LE",
+ "U24_BE",
+ "S32_LE",
+ "S32_BE",
+ "U32_LE",
+ "U32_BE",
+ "FLOAT_LE",
+ "FLOAT_BE",
+ "FLOAT64_LE",
+ "FLOAT64_BE",
+ "IEC958_SUBFRAME_LE",
+ "IEC958_SUBFRAME_BE",
+ "MU_LAW",
+ "A_LAW",
+ "IMA_ADPCM",
+ "MPEG",
+ /*[24] =*/ "GSM",
+ [31] = "SPECIAL",
+ "S24_3LE",
+ "S24_3BE",
+ "U24_3LE",
+ "U24_3BE",
+ "S20_3LE",
+ "S20_3BE",
+ "U20_3LE",
+ "U20_3BE",
+ "S18_3LE",
+ "S18_3BE",
+ "U18_3LE",
+ /*[43] =*/ "U18_3BE",
+#if 0
+ /* recent additions, may not be present on local asound.h */
+ "G723_24",
+ "G723_24_1B",
+ "G723_40",
+ "G723_40_1B",
+ "DSD_U8",
+ "DSD_U16_LE",
+#endif
+};
+
+/* Returns a human readable name for the format associated with bit_index,
+ * NULL if bit_index is not known.
+ */
+static inline const char *pcm_get_format_name(unsigned bit_index)
+{
+ return bit_index < ARRAY_SIZE(format_lookup) ? format_lookup[bit_index] : NULL;
+}
+
+int main(int argc, char **argv)
+{
+ unsigned int device = 0;
+ unsigned int card = 0;
+ int i;
+ struct optparse opts;
+ struct optparse_long long_options[] = {
+ { "help", 'h', OPTPARSE_NONE },
+ { "card", 'D', OPTPARSE_REQUIRED },
+ { "device", 'd', OPTPARSE_REQUIRED },
+ { 0, 0, 0 }
+ };
+
+ (void)argc; /* silence -Wunused-parameter */
+ /* parse command line arguments */
+ optparse_init(&opts, argv);
+ while ((i = optparse_long(&opts, long_options, NULL)) != -1) {
+ switch (i) {
+ case 'D':
+ card = atoi(opts.optarg);
+ break;
+ case 'd':
+ device = atoi(opts.optarg);
+ break;
+ case 'h':
+ fprintf(stderr, "Usage: %s -D card -d device\n", argv[0]);
+ return 0;
+ case '?':
+ fprintf(stderr, "%s\n", opts.errmsg);
+ return EXIT_FAILURE;
+ }
+ }
+
+ printf("Info for card %u, device %u:\n", card, device);
+
+ for (i = 0; i < 2; i++) {
+ struct pcm_params *params;
+ const struct pcm_mask *m;
+ unsigned int min;
+ unsigned int max;
+
+ printf("\nPCM %s:\n", i == 0 ? "out" : "in");
+
+ params = pcm_params_get(card, device, i == 0 ? PCM_OUT : PCM_IN);
+ if (params == NULL) {
+ printf("Device does not exist.\n");
+ continue;
+ }
+
+ m = pcm_params_get_mask(params, PCM_PARAM_ACCESS);
+ if (m) { /* bitmask, refer to SNDRV_PCM_ACCESS_*, generally interleaved */
+ printf(" Access:\t%#08x\n", m->bits[0]);
+ }
+ m = pcm_params_get_mask(params, PCM_PARAM_FORMAT);
+ if (m) { /* bitmask, refer to: SNDRV_PCM_FORMAT_* */
+ unsigned j, k, count = 0;
+ const unsigned bitcount = sizeof(m->bits[0]) * 8;
+
+ /* we only check first two format masks (out of 8) - others are zero. */
+ printf(" Format[0]:\t%#08x\n", m->bits[0]);
+ printf(" Format[1]:\t%#08x\n", m->bits[1]);
+
+ /* print friendly format names, if they exist */
+ for (k = 0; k < 2; ++k) {
+ for (j = 0; j < bitcount; ++j) {
+ const char *name;
+
+ if (m->bits[k] & (1 << j)) {
+ name = pcm_get_format_name(j + k*bitcount);
+ if (name) {
+ if (count++ == 0) {
+ printf(" Format Name:\t");
+ } else {
+ printf (", ");
+ }
+ printf("%s", name);
+ }
+ }
+ }
+ }
+ if (count) {
+ printf("\n");
+ }
+ }
+ m = pcm_params_get_mask(params, PCM_PARAM_SUBFORMAT);
+ if (m) { /* bitmask, should be 1: SNDRV_PCM_SUBFORMAT_STD */
+ printf(" Subformat:\t%#08x\n", m->bits[0]);
+ }
+ min = pcm_params_get_min(params, PCM_PARAM_RATE);
+ max = pcm_params_get_max(params, PCM_PARAM_RATE);
+ printf(" Rate:\tmin=%uHz\tmax=%uHz\n", min, max);
+ min = pcm_params_get_min(params, PCM_PARAM_CHANNELS);
+ max = pcm_params_get_max(params, PCM_PARAM_CHANNELS);
+ printf(" Channels:\tmin=%u\t\tmax=%u\n", min, max);
+ min = pcm_params_get_min(params, PCM_PARAM_SAMPLE_BITS);
+ max = pcm_params_get_max(params, PCM_PARAM_SAMPLE_BITS);
+ printf(" Sample bits:\tmin=%u\t\tmax=%u\n", min, max);
+ min = pcm_params_get_min(params, PCM_PARAM_PERIOD_SIZE);
+ max = pcm_params_get_max(params, PCM_PARAM_PERIOD_SIZE);
+ printf(" Period size:\tmin=%u\t\tmax=%u\n", min, max);
+ min = pcm_params_get_min(params, PCM_PARAM_PERIODS);
+ max = pcm_params_get_max(params, PCM_PARAM_PERIODS);
+ printf("Period count:\tmin=%u\t\tmax=%u\n", min, max);
+
+ pcm_params_free(params);
+ }
+
+ return 0;
+}
diff --git a/utils/tinyplay.1 b/utils/tinyplay.1
new file mode 100644
index 0000000..56bac12
--- /dev/null
+++ b/utils/tinyplay.1
@@ -0,0 +1,94 @@
+.TH TINYPLAY 1 "October 2, 2016" "tinyplay" "TinyALSA"
+
+.SH NAME
+tinyplay \- sends audio to an audio device
+
+.SH SYNOPSIS
+.B tinyplay\fR \fIfile\fR [ \fIoptions\fR ]
+
+.SH Description
+
+\fBtinyplay\fR can send audio to an audio device from a wav file or standard input (as raw samples).
+Options can be used to specify various hardware parameters to open the PCM with.
+
+.SH OPTIONS
+
+.TP
+\fB\-D, --card\fR \fIcard\fR
+Card number of the PCM.
+The default is 0.
+
+.TP
+\fB\-d, --device\fR \fIdevice\fR
+Device number of the PCM.
+The default is 0.
+
+.TP
+\fB\-c, --channels\fR \fIchannels\fR
+Number of channels the PCM will have.
+This option is only valid for raw file types.
+The default is 2 for raw file types.
+
+.TP
+\fB\-r, --rate\fR \fIrate\fR
+Number of frames per second of the PCM.
+This option is only valid for raw file types.
+The default is 48000 for raw file types.
+
+.TP
+\fB\-i, --file-type\fR \fIfile-type\fR
+The file type used for playback.
+Available types are \fIraw\fR and \fIwav\fR.
+Specifying \fIraw\fR means that \fIchannels\fR, \fIrate\fR and \fIbits\fR may have to be specified as well.
+By default, the file type is determined by the file name.
+Specifying the file type with this option will take precedent over the one determined by the file name.
+
+.TP
+\fB\-b, --bits\fR \fIbits\fR
+Number of bits per sample the PCM will have.
+This option is only valid for raw file types.
+The default is 16 for raw file types.
+
+.TP
+\fB\-p, --period-size\fR \fIperiod_size\fR
+Number of frames in a period.
+The default is 1024.
+
+.TP
+\fB\-n, --period-count\fR \fIperiods\fR
+Number of periods the PCM will have.
+The default is 4.
+
+.SH SIGNALS
+
+When playing audio, SIGINT will stop the playback and close the file.
+
+.SH EXAMPLES
+
+.TP
+\fBtinyplay output.wav\fR
+Plays a file called output.wav.
+
+.TP
+\fBtinyplay output.wav -D 1
+Plays a file called output.wav on card 1.
+
+.TP
+\fBtinyplay output.raw -i raw --channels 2 --rate 44100 --bits 32
+Plays a raw audio file called output.raw; using 2 channels, 44100 frames per second and 32 bits per sample.
+
+.SH BUGS
+
+Please report bugs to https://github.com/tinyalsa/tinyalsa/issues.
+
+.SH SEE ALSO
+
+.BR tinycap(1),
+.BR tinymix(1),
+.BR tinypcminfo(1)
+
+.SH AUTHORS
+Simon Wilson
+.P
+For a complete list of authors, visit the project page at https://github.com/tinyalsa/tinyalsa.
+
diff --git a/utils/tinyplay.c b/utils/tinyplay.c
new file mode 100644
index 0000000..2689158
--- /dev/null
+++ b/utils/tinyplay.c
@@ -0,0 +1,435 @@
+/* tinyplay.c
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+
+#include <tinyalsa/asoundlib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <signal.h>
+
+#define OPTPARSE_IMPLEMENTATION
+#include "optparse.h"
+
+struct cmd {
+ const char *filename;
+ const char *filetype;
+ unsigned int card;
+ unsigned int device;
+ int flags;
+ struct pcm_config config;
+ unsigned int bits;
+};
+
+void cmd_init(struct cmd *cmd)
+{
+ cmd->filename = NULL;
+ cmd->filetype = NULL;
+ cmd->card = 0;
+ cmd->device = 0;
+ cmd->flags = PCM_OUT;
+ cmd->config.period_size = 1024;
+ cmd->config.period_count = 2;
+ cmd->config.channels = 2;
+ cmd->config.rate = 48000;
+ cmd->config.format = PCM_FORMAT_S16_LE;
+ cmd->config.silence_threshold = cmd->config.period_size * cmd->config.period_count;
+ cmd->config.silence_size = 0;
+ cmd->config.stop_threshold = cmd->config.period_size * cmd->config.period_count;
+ cmd->config.start_threshold = cmd->config.period_size;
+ cmd->bits = 16;
+}
+
+#define ID_RIFF 0x46464952
+#define ID_WAVE 0x45564157
+#define ID_FMT 0x20746d66
+#define ID_DATA 0x61746164
+
+struct riff_wave_header {
+ uint32_t riff_id;
+ uint32_t riff_sz;
+ uint32_t wave_id;
+};
+
+struct chunk_header {
+ uint32_t id;
+ uint32_t sz;
+};
+
+struct chunk_fmt {
+ uint16_t audio_format;
+ uint16_t num_channels;
+ uint32_t sample_rate;
+ uint32_t byte_rate;
+ uint16_t block_align;
+ uint16_t bits_per_sample;
+};
+
+struct ctx {
+ struct pcm *pcm;
+
+ struct riff_wave_header wave_header;
+ struct chunk_header chunk_header;
+ struct chunk_fmt chunk_fmt;
+
+ FILE *file;
+};
+
+int ctx_init(struct ctx* ctx, const struct cmd *cmd)
+{
+ unsigned int bits = cmd->bits;
+ struct pcm_config config = cmd->config;
+
+ if (cmd->filename == NULL) {
+ fprintf(stderr, "filename not specified\n");
+ return -1;
+ }
+ if (strcmp(cmd->filename, "-") == 0) {
+ ctx->file = stdin;
+ } else {
+ ctx->file = fopen(cmd->filename, "rb");
+ }
+
+ if (ctx->file == NULL) {
+ fprintf(stderr, "failed to open '%s'\n", cmd->filename);
+ return -1;
+ }
+
+ if ((cmd->filetype != NULL) && (strcmp(cmd->filetype, "wav") == 0)) {
+ if (fread(&ctx->wave_header, sizeof(ctx->wave_header), 1, ctx->file) != 1){
+ fprintf(stderr, "error: '%s' does not contain a riff/wave header\n", cmd->filename);
+ fclose(ctx->file);
+ return -1;
+ }
+ if ((ctx->wave_header.riff_id != ID_RIFF) ||
+ (ctx->wave_header.wave_id != ID_WAVE)) {
+ fprintf(stderr, "error: '%s' is not a riff/wave file\n", cmd->filename);
+ fclose(ctx->file);
+ return -1;
+ }
+ unsigned int more_chunks = 1;
+ do {
+ if (fread(&ctx->chunk_header, sizeof(ctx->chunk_header), 1, ctx->file) != 1){
+ fprintf(stderr, "error: '%s' does not contain a data chunk\n", cmd->filename);
+ fclose(ctx->file);
+ return -1;
+ }
+ switch (ctx->chunk_header.id) {
+ case ID_FMT:
+ if (fread(&ctx->chunk_fmt, sizeof(ctx->chunk_fmt), 1, ctx->file) != 1){
+ fprintf(stderr, "error: '%s' has incomplete format chunk\n", cmd->filename);
+ fclose(ctx->file);
+ return -1;
+ }
+ /* If the format header is larger, skip the rest */
+ if (ctx->chunk_header.sz > sizeof(ctx->chunk_fmt))
+ fseek(ctx->file, ctx->chunk_header.sz - sizeof(ctx->chunk_fmt), SEEK_CUR);
+ break;
+ case ID_DATA:
+ /* Stop looking for chunks */
+ more_chunks = 0;
+ break;
+ default:
+ /* Unknown chunk, skip bytes */
+ fseek(ctx->file, ctx->chunk_header.sz, SEEK_CUR);
+ }
+ } while (more_chunks);
+ config.channels = ctx->chunk_fmt.num_channels;
+ config.rate = ctx->chunk_fmt.sample_rate;
+ bits = ctx->chunk_fmt.bits_per_sample;
+ }
+
+ if (bits == 8) {
+ config.format = PCM_FORMAT_S8;
+ } else if (bits == 16) {
+ config.format = PCM_FORMAT_S16_LE;
+ } else if (bits == 24) {
+ config.format = PCM_FORMAT_S24_3LE;
+ } else if (bits == 32) {
+ config.format = PCM_FORMAT_S32_LE;
+ } else {
+ fprintf(stderr, "bit count '%u' not supported\n", bits);
+ fclose(ctx->file);
+ return -1;
+ }
+
+ ctx->pcm = pcm_open(cmd->card,
+ cmd->device,
+ cmd->flags,
+ &config);
+ if (ctx->pcm == NULL) {
+ fprintf(stderr, "failed to allocate memory for pcm\n");
+ fclose(ctx->file);
+ return -1;
+ } else if (!pcm_is_ready(ctx->pcm)) {
+ fprintf(stderr, "failed to open for pcm %u,%u\n", cmd->card, cmd->device);
+ fclose(ctx->file);
+ pcm_close(ctx->pcm);
+ return -1;
+ }
+
+ return 0;
+}
+
+void ctx_free(struct ctx *ctx)
+{
+ if (ctx == NULL) {
+ return;
+ }
+ if (ctx->pcm != NULL) {
+ pcm_close(ctx->pcm);
+ }
+ if (ctx->file != NULL) {
+ fclose(ctx->file);
+ }
+}
+
+static int close = 0;
+
+int play_sample(struct ctx *ctx);
+
+void stream_close(int sig)
+{
+ /* allow the stream to be closed gracefully */
+ signal(sig, SIG_IGN);
+ close = 1;
+}
+
+void print_usage(const char *argv0)
+{
+ fprintf(stderr, "usage: %s file.wav [options]\n", argv0);
+ fprintf(stderr, "options:\n");
+ fprintf(stderr, "-D | --card <card number> The card to receive the audio\n");
+ fprintf(stderr, "-d | --device <device number> The device to receive the audio\n");
+ fprintf(stderr, "-p | --period-size <size> The size of the PCM's period\n");
+ fprintf(stderr, "-n | --period-count <count> The number of PCM periods\n");
+ fprintf(stderr, "-i | --file-type <file-type > The type of file to read (raw or wav)\n");
+ fprintf(stderr, "-c | --channels <count> The amount of channels per frame\n");
+ fprintf(stderr, "-r | --rate <rate> The amount of frames per second\n");
+ fprintf(stderr, "-b | --bits <bit-count> The number of bits in one sample\n");
+ fprintf(stderr, "-M | --mmap Use memory mapped IO to play audio\n");
+}
+
+int main(int argc, char **argv)
+{
+ int c;
+ struct cmd cmd;
+ struct ctx ctx;
+ struct optparse opts;
+ struct optparse_long long_options[] = {
+ { "card", 'D', OPTPARSE_REQUIRED },
+ { "device", 'd', OPTPARSE_REQUIRED },
+ { "period-size", 'p', OPTPARSE_REQUIRED },
+ { "period-count", 'n', OPTPARSE_REQUIRED },
+ { "file-type", 'i', OPTPARSE_REQUIRED },
+ { "channels", 'c', OPTPARSE_REQUIRED },
+ { "rate", 'r', OPTPARSE_REQUIRED },
+ { "bits", 'b', OPTPARSE_REQUIRED },
+ { "mmap", 'M', OPTPARSE_NONE },
+ { "help", 'h', OPTPARSE_NONE },
+ { 0, 0, 0 }
+ };
+
+ if (argc < 2) {
+ print_usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ cmd_init(&cmd);
+ optparse_init(&opts, argv);
+ while ((c = optparse_long(&opts, long_options, NULL)) != -1) {
+ switch (c) {
+ case 'D':
+ if (sscanf(opts.optarg, "%u", &cmd.card) != 1) {
+ fprintf(stderr, "failed parsing card number '%s'\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+ break;
+ case 'd':
+ if (sscanf(opts.optarg, "%u", &cmd.device) != 1) {
+ fprintf(stderr, "failed parsing device number '%s'\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+ break;
+ case 'p':
+ if (sscanf(opts.optarg, "%u", &cmd.config.period_size) != 1) {
+ fprintf(stderr, "failed parsing period size '%s'\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+ break;
+ case 'n':
+ if (sscanf(opts.optarg, "%u", &cmd.config.period_count) != 1) {
+ fprintf(stderr, "failed parsing period count '%s'\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+ break;
+ case 'c':
+ if (sscanf(opts.optarg, "%u", &cmd.config.channels) != 1) {
+ fprintf(stderr, "failed parsing channel count '%s'\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+ break;
+ case 'r':
+ if (sscanf(opts.optarg, "%u", &cmd.config.rate) != 1) {
+ fprintf(stderr, "failed parsing rate '%s'\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+ break;
+ case 'i':
+ cmd.filetype = opts.optarg;
+ break;
+ case 'h':
+ print_usage(argv[0]);
+ return EXIT_SUCCESS;
+ case '?':
+ fprintf(stderr, "%s\n", opts.errmsg);
+ return EXIT_FAILURE;
+ }
+ }
+ cmd.filename = optparse_arg(&opts);
+
+ if (cmd.filename != NULL && cmd.filetype == NULL &&
+ (cmd.filetype = strrchr(cmd.filename, '.')) != NULL) {
+ cmd.filetype++;
+ }
+
+ cmd.config.silence_threshold = cmd.config.period_size * cmd.config.period_count;
+ cmd.config.stop_threshold = cmd.config.period_size * cmd.config.period_count;
+ cmd.config.start_threshold = cmd.config.period_size;
+
+ if (ctx_init(&ctx, &cmd) < 0) {
+ return EXIT_FAILURE;
+ }
+
+ /* TODO get parameters from context */
+ printf("playing '%s': %u ch, %u hz, %u bit\n",
+ cmd.filename,
+ cmd.config.channels,
+ cmd.config.rate,
+ cmd.bits);
+
+ if (play_sample(&ctx) < 0) {
+ ctx_free(&ctx);
+ return EXIT_FAILURE;
+ }
+
+ ctx_free(&ctx);
+ return EXIT_SUCCESS;
+}
+
+int check_param(struct pcm_params *params, unsigned int param, unsigned int value,
+ char *param_name, char *param_unit)
+{
+ unsigned int min;
+ unsigned int max;
+ int is_within_bounds = 1;
+
+ min = pcm_params_get_min(params, param);
+ if (value < min) {
+ fprintf(stderr, "%s is %u%s, device only supports >= %u%s\n", param_name, value,
+ param_unit, min, param_unit);
+ is_within_bounds = 0;
+ }
+
+ max = pcm_params_get_max(params, param);
+ if (value > max) {
+ fprintf(stderr, "%s is %u%s, device only supports <= %u%s\n", param_name, value,
+ param_unit, max, param_unit);
+ is_within_bounds = 0;
+ }
+
+ return is_within_bounds;
+}
+
+int sample_is_playable(const struct cmd *cmd)
+{
+ struct pcm_params *params;
+ int can_play;
+
+ params = pcm_params_get(cmd->card, cmd->device, PCM_OUT);
+ if (params == NULL) {
+ fprintf(stderr, "unable to open PCM %u,%u\n", cmd->card, cmd->device);
+ return 0;
+ }
+
+ can_play = check_param(params, PCM_PARAM_RATE, cmd->config.rate, "sample rate", "hz");
+ can_play &= check_param(params, PCM_PARAM_CHANNELS, cmd->config.channels, "sample", " channels");
+ can_play &= check_param(params, PCM_PARAM_SAMPLE_BITS, cmd->bits, "bits", " bits");
+ can_play &= check_param(params, PCM_PARAM_PERIOD_SIZE, cmd->config.period_size, "period size",
+ " frames");
+ can_play &= check_param(params, PCM_PARAM_PERIODS, cmd->config.period_count, "period count",
+ " frames");
+
+ pcm_params_free(params);
+
+ return can_play;
+}
+
+int play_sample(struct ctx *ctx)
+{
+ char *buffer;
+ size_t buffer_size = 0;
+ size_t num_read = 0;
+ size_t remaining_data_size = ctx->chunk_header.sz;
+ size_t read_size = 0;
+ const struct pcm_config *config = pcm_get_config(ctx->pcm);
+
+ if (config == NULL) {
+ fprintf(stderr, "unable to get pcm config\n");
+ return -1;
+ }
+
+ buffer_size = pcm_frames_to_bytes(ctx->pcm, config->period_size);
+ buffer = malloc(buffer_size);
+ if (!buffer) {
+ fprintf(stderr, "unable to allocate %zu bytes\n", buffer_size);
+ return -1;
+ }
+
+ /* catch ctrl-c to shutdown cleanly */
+ signal(SIGINT, stream_close);
+
+ do {
+ read_size = remaining_data_size > buffer_size ? buffer_size : remaining_data_size;
+ num_read = fread(buffer, 1, read_size, ctx->file);
+ if (num_read > 0) {
+ if (pcm_writei(ctx->pcm, buffer,
+ pcm_bytes_to_frames(ctx->pcm, num_read)) < 0) {
+ fprintf(stderr, "error playing sample\n");
+ break;
+ }
+ remaining_data_size -= num_read;
+ }
+ } while (!close && num_read > 0 && remaining_data_size > 0);
+
+ pcm_wait(ctx->pcm, -1);
+
+ free(buffer);
+ return 0;
+}
+
diff --git a/utils/tinywavinfo.c b/utils/tinywavinfo.c
new file mode 100644
index 0000000..a74ca7d
--- /dev/null
+++ b/utils/tinywavinfo.c
@@ -0,0 +1,224 @@
+/* tinywavinfo.c
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of The Android Open Source Project nor the names of
+** its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+** DAMAGE.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <signal.h>
+#include <math.h>
+
+#define ID_RIFF 0x46464952
+#define ID_WAVE 0x45564157
+#define ID_FMT 0x20746d66
+#define ID_DATA 0x61746164
+
+struct riff_wave_header {
+ uint32_t riff_id;
+ uint32_t riff_sz;
+ uint32_t wave_id;
+};
+
+struct chunk_header {
+ uint32_t id;
+ uint32_t sz;
+};
+
+struct chunk_fmt {
+ uint16_t audio_format;
+ uint16_t num_channels;
+ uint32_t sample_rate;
+ uint32_t byte_rate;
+ uint16_t block_align;
+ uint16_t bits_per_sample;
+};
+
+static int close = 0;
+
+void analyse_sample(FILE *file, unsigned int channels, unsigned int bits,
+ unsigned int data_chunk_size);
+
+void stream_close(int sig)
+{
+ /* allow the stream to be closed gracefully */
+ signal(sig, SIG_IGN);
+ close = 1;
+}
+
+size_t xfread(void *ptr, size_t size, size_t nmemb, FILE *stream)
+{
+ size_t sz = fread(ptr, size, nmemb, stream);
+
+ if (sz != nmemb && ferror(stream)) {
+ fprintf(stderr, "Error: fread failed\n");
+ exit(1);
+ }
+ return sz;
+}
+
+int main(int argc, char **argv)
+{
+ FILE *file;
+ struct riff_wave_header riff_wave_header;
+ struct chunk_header chunk_header;
+ struct chunk_fmt chunk_fmt;
+ char *filename;
+ int more_chunks = 1;
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s file.wav \n", argv[0]);
+ return 1;
+ }
+
+ filename = argv[1];
+ file = fopen(filename, "rb");
+ if (!file) {
+ fprintf(stderr, "Unable to open file '%s'\n", filename);
+ return 1;
+ }
+
+ xfread(&riff_wave_header, sizeof(riff_wave_header), 1, file);
+ if ((riff_wave_header.riff_id != ID_RIFF) ||
+ (riff_wave_header.wave_id != ID_WAVE)) {
+ fprintf(stderr, "Error: '%s' is not a riff/wave file\n", filename);
+ fclose(file);
+ return 1;
+ }
+
+ do {
+ xfread(&chunk_header, sizeof(chunk_header), 1, file);
+
+ switch (chunk_header.id) {
+ case ID_FMT:
+ xfread(&chunk_fmt, sizeof(chunk_fmt), 1, file);
+ /* If the format header is larger, skip the rest */
+ if (chunk_header.sz > sizeof(chunk_fmt))
+ fseek(file, chunk_header.sz - sizeof(chunk_fmt), SEEK_CUR);
+ break;
+ case ID_DATA:
+ /* Stop looking for chunks */
+ more_chunks = 0;
+ break;
+ default:
+ /* Unknown chunk, skip bytes */
+ fseek(file, chunk_header.sz, SEEK_CUR);
+ }
+ } while (more_chunks);
+
+ printf("Input File : %s \n", filename);
+ printf("Channels : %u \n", chunk_fmt.num_channels);
+ printf("Sample Rate : %u \n", chunk_fmt.sample_rate);
+ printf("Bits per sample : %u \n\n", chunk_fmt.bits_per_sample);
+
+ analyse_sample(file, chunk_fmt.num_channels, chunk_fmt.bits_per_sample,
+ chunk_header.sz);
+
+ fclose(file);
+
+ return 0;
+}
+
+void analyse_sample(FILE *file, unsigned int channels, unsigned int bits,
+ unsigned int data_chunk_size)
+{
+ void *buffer;
+ int size;
+ int num_read;
+ int i;
+ unsigned int ch;
+ int frame_size = 1024;
+ unsigned int bytes_per_sample = 0;
+ float *power;
+ int total_sample_per_channel;
+ float normalization_factor;
+
+ if (bits == 32)
+ bytes_per_sample = 4;
+ else if (bits == 16)
+ bytes_per_sample = 2;
+
+ normalization_factor = (float)pow(2.0, (bits-1));
+
+ size = channels * bytes_per_sample * frame_size;
+
+ buffer = malloc(size);
+ if (!buffer) {
+ fprintf(stderr, "Unable to allocate %d bytes\n", size);
+ free(buffer);
+ return;
+ }
+
+ power = (float *) calloc(channels, sizeof(float));
+
+ total_sample_per_channel = data_chunk_size / (channels * bytes_per_sample);
+
+ /* catch ctrl-c to shutdown cleanly */
+ signal(SIGINT, stream_close);
+
+ do {
+ num_read = xfread(buffer, 1, size, file);
+ if (num_read > 0) {
+ if (2 == bytes_per_sample) {
+ short *buffer_ptr = (short *)buffer;
+ for (i = 0; i < num_read; i += channels) {
+ for (ch = 0; ch < channels; ch++) {
+ int temp = *buffer_ptr++;
+ /* Signal Normalization */
+ float f = (float) temp / normalization_factor;
+ *(power + ch) += (float) (f * f);
+ }
+ }
+ }
+ if (4 == bytes_per_sample) {
+ int *buffer_ptr = (int *)buffer;
+ for (i = 0; i < num_read; i += channels) {
+ for (ch = 0; ch < channels; ch++) {
+ int temp = *buffer_ptr++;
+ /* Signal Normalization */
+ float f = (float) temp / normalization_factor;
+ *(power + ch) += (float) (f * f);
+ }
+ }
+ }
+ }
+ }while (!close && num_read > 0);
+
+ for (ch = 0; ch < channels; ch++) {
+ float average_power = 10 * log10((*(power + ch)) / total_sample_per_channel);
+ if(isinf (average_power)) {
+ printf("Channel [%2u] Average Power : NO signal or ZERO signal\n", ch);
+ } else {
+ printf("Channel [%2u] Average Power : %.2f dB\n", ch, average_power);
+ }
+ }
+
+ free(buffer);
+ free(power);
+
+}
+