├── LICENSE ├── MODULE_LICENSE_BSD ├── debian ├── compat ├── tinyalsa.dirs ├── libtinyalsa.dirs.in ├── libtinyalsa-dev.dirs.in ├── libtinyalsa.install.in ├── libtinyalsa-dev.install.in ├── tinyalsa.install ├── rules ├── control ├── changelog └── copyright ├── OWNERS ├── METADATA ├── examples ├── sndcardparser │ ├── Android.bp │ └── sample_sndcardparser.c ├── meson.build ├── Makefile ├── plugins │ ├── Android.bp │ ├── sample_pcm_plugin.c │ └── sample_mixer_plugin.c ├── pcm-readi.c └── pcm-writei.c ├── WORKSPACE ├── include └── tinyalsa │ ├── meson.build │ ├── attributes.h │ ├── asoundlib.h │ ├── interval.h │ ├── limits.h │ ├── version.h │ ├── mixer.h │ └── plugin.h ├── utils ├── meson.build ├── tinypcminfo.1 ├── Makefile ├── tinymix.1 ├── tinycap.1 ├── tinyplay.1 ├── tinywavinfo.c ├── tinypcminfo.c ├── tinycap.c └── optparse.h ├── .gitignore ├── scripts ├── travis-build.sh └── version.sh ├── meson_options.txt ├── src ├── limits.c ├── Makefile ├── mixer_io.h ├── pcm_io.h ├── snd_card_plugin.h ├── mixer_hw.c ├── snd_card_plugin.c └── pcm_hw.c ├── doxygen ├── mainpage.h └── Makefile ├── .travis.yml ├── meson.build ├── Makefile ├── NOTICE ├── BUILD ├── Android.bp ├── tests ├── include │ └── pcm_test_device.h └── src │ ├── pcm_in_test.cc │ ├── pcm_test.cc │ ├── pcm_out_test.cc │ ├── pcm_loopback_test.cc │ └── pcm_params_test.cc ├── CMakeLists.txt └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | NOTICE -------------------------------------------------------------------------------- /MODULE_LICENSE_BSD: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/tinyalsa.dirs: -------------------------------------------------------------------------------- 1 | /usr/bin 2 | /usr/share 3 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | dvdli@google.com 2 | gkasten@google.com 3 | -------------------------------------------------------------------------------- /METADATA: -------------------------------------------------------------------------------- 1 | third_party { 2 | license_type: NOTICE 3 | } 4 | -------------------------------------------------------------------------------- /debian/libtinyalsa.dirs.in: -------------------------------------------------------------------------------- 1 | /usr/lib/@DEB_HOST_MULTIARCH@ 2 | -------------------------------------------------------------------------------- /debian/libtinyalsa-dev.dirs.in: -------------------------------------------------------------------------------- 1 | /usr/lib/@DEB_HOST_MULTIARCH@ 2 | /usr/include 3 | -------------------------------------------------------------------------------- /debian/libtinyalsa.install.in: -------------------------------------------------------------------------------- 1 | debian/tmp/usr/lib/@DEB_HOST_MULTIARCH@/*.so.* usr/lib/@DEB_HOST_MULTIARCH@/ 2 | -------------------------------------------------------------------------------- /examples/sndcardparser/Android.bp: -------------------------------------------------------------------------------- 1 | cc_library { 2 | name: "libsndcardparser_example", 3 | vendor: true, 4 | srcs: ["sample_sndcardparser.c"], 5 | cflags: ["-Werror"], 6 | } 7 | 8 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") 2 | 3 | git_repository( 4 | name = "googletest", 5 | remote = "https://github.com/google/googletest", 6 | branch = "main", 7 | ) 8 | -------------------------------------------------------------------------------- /examples/meson.build: -------------------------------------------------------------------------------- 1 | examples = ['pcm-readi', 'pcm-writei'] 2 | 3 | foreach e : examples 4 | executable(e, '@0@.c'.format(e), 5 | include_directories: tinyalsa_includes, 6 | link_with: tinyalsa, 7 | install: false) 8 | endforeach 9 | -------------------------------------------------------------------------------- /debian/libtinyalsa-dev.install.in: -------------------------------------------------------------------------------- 1 | debian/tmp/usr/include/* usr/include/ 2 | debian/tmp/usr/lib/@DEB_HOST_MULTIARCH@/lib*.so usr/lib/@DEB_HOST_MULTIARCH@/ 3 | debian/tmp/usr/lib/@DEB_HOST_MULTIARCH@/lib*.a usr/lib/@DEB_HOST_MULTIARCH@/ 4 | debian/tmp/usr/share/man/man3 usr/share/man/ 5 | -------------------------------------------------------------------------------- /include/tinyalsa/meson.build: -------------------------------------------------------------------------------- 1 | tinyalsa_headers = [ 2 | 'asoundlib.h', 3 | 'attributes.h', 4 | 'interval.h', 5 | 'limits.h', 6 | 'mixer.h', 7 | 'pcm.h', 8 | 'plugin.h', 9 | 'version.h' 10 | ] 11 | 12 | install_headers(tinyalsa_headers, subdir: 'tinyalsa') 13 | -------------------------------------------------------------------------------- /utils/meson.build: -------------------------------------------------------------------------------- 1 | utils = ['tinyplay', 'tinycap', 'tinymix', 'tinypcminfo'] 2 | 3 | foreach util : utils 4 | executable(util, '@0@.c'.format(util), 5 | include_directories: tinyalsa_includes, 6 | link_with: tinyalsa, 7 | install: true) 8 | install_man('@0@.1'.format(util)) 9 | endforeach 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /doxygen/html 2 | /doxygen/man 3 | 4 | /build 5 | 6 | *.o 7 | *.so 8 | *.so.* 9 | *.a 10 | 11 | /libs 12 | /obj 13 | 14 | /examples/audio.raw 15 | /examples/pcm-readi 16 | /examples/pcm-writei 17 | 18 | /utils/tinyplay 19 | /utils/tinycap 20 | /utils/tinymix 21 | /utils/tinypcminfo 22 | 23 | /bazel* 24 | -------------------------------------------------------------------------------- /scripts/travis-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -u 5 | 6 | ROOT=$(pwd) 7 | 8 | make 9 | make clean 10 | 11 | mkdir cmake-build 12 | cd cmake-build 13 | cmake .. 14 | cmake --build . 15 | cd .. 16 | 17 | $HOME/.local/bin/meson . meson-build 18 | cd meson-build 19 | ninja 20 | cd .. 21 | 22 | ${ROOT}/scripts/version.sh check 23 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('docs', type: 'feature', value: 'auto', yield: true, 2 | description : 'Generate documentation with Doxygen') 3 | option('examples', type: 'feature', value: 'auto', yield: true, 4 | description : 'Build examples') 5 | option('utils', type: 'feature', value: 'auto', yield: true, 6 | description : 'Build utility tools') 7 | -------------------------------------------------------------------------------- /src/limits.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | const struct tinyalsa_unsigned_interval tinyalsa_channels_limit = { 4 | .max = TINYALSA_CHANNELS_MAX, 5 | .min = TINYALSA_CHANNELS_MIN 6 | }; 7 | 8 | const struct tinyalsa_unsigned_interval tinyalsa_frames_limit = { 9 | .max = TINYALSA_FRAMES_MAX, 10 | .min = TINYALSA_FRAMES_MIN 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /debian/tinyalsa.install: -------------------------------------------------------------------------------- 1 | debian/tmp/usr/bin/tinyplay usr/bin/ 2 | debian/tmp/usr/bin/tinycap usr/bin/ 3 | debian/tmp/usr/bin/tinymix usr/bin/ 4 | debian/tmp/usr/bin/tinypcminfo usr/bin/ 5 | debian/tmp/usr/share/man/man1/tinyplay.1 usr/share/man/man1/ 6 | debian/tmp/usr/share/man/man1/tinycap.1 usr/share/man/man1/ 7 | debian/tmp/usr/share/man/man1/tinymix.1 usr/share/man/man1/ 8 | debian/tmp/usr/share/man/man1/tinypcminfo.1 usr/share/man/man1/ 9 | -------------------------------------------------------------------------------- /doxygen/mainpage.h: -------------------------------------------------------------------------------- 1 | /** @mainpage Preamble 2 | * 3 | * Welcome to the documentation for the TinyALSA project. 4 | *

5 | * To start, you may either view the @ref libtinyalsa-pcm or @ref libtinyalsa-mixer. 6 | *

7 | * If you find an error in the documentation or an area for improvement, 8 | * open an issue or send a pull request to the github page. 9 | */ 10 | 11 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | CROSS_COMPILE ?= 2 | 3 | CC = $(CROSS_COMPILE)gcc 4 | CFLAGS = -Wall -Wextra -Werror -Wfatal-errors -I ../include 5 | 6 | VPATH = ../src 7 | 8 | EXAMPLES += pcm-readi 9 | EXAMPLES += pcm-writei 10 | 11 | .PHONY: all 12 | all: $(EXAMPLES) 13 | 14 | pcm-readi pcm-writei: LDLIBS+=-ldl 15 | 16 | pcm-readi: pcm-readi.c -ltinyalsa 17 | 18 | pcm-writei: pcm-writei.c -ltinyalsa 19 | 20 | .PHONY: clean 21 | clean: 22 | rm -f $(EXAMPLES) 23 | 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux 2 | dist: bionic 3 | sudo: false 4 | language: c 5 | compiler: 6 | - gcc 7 | - clang 8 | 9 | addons: 10 | apt: 11 | packages: 12 | - doxygen 13 | - graphviz 14 | - python3-pip 15 | - python3-setuptools 16 | 17 | before_script: 18 | - pip3 install --upgrade pip 19 | - pip3 install --user ninja 20 | - pip3 install --user meson 21 | 22 | script: 23 | - scripts/travis-build.sh 24 | -------------------------------------------------------------------------------- /examples/plugins/Android.bp: -------------------------------------------------------------------------------- 1 | cc_library { 2 | name: "libtinyalsav2_example_plugin_pcm", 3 | vendor: true, 4 | srcs: ["sample_pcm_plugin.c"], 5 | cflags: ["-Werror", "-Wno-unused-parameter"], 6 | header_libs: ["libtinyalsav2_headers"], 7 | } 8 | 9 | cc_library { 10 | name: "libtinyalsav2_example_plugin_mixer", 11 | vendor: true, 12 | srcs: ["sample_mixer_plugin.c"], 13 | cflags: ["-Werror", "-Wno-unused-parameter"], 14 | header_libs: ["libtinyalsav2_headers"], 15 | } 16 | -------------------------------------------------------------------------------- /doxygen/Makefile: -------------------------------------------------------------------------------- 1 | DESTDIR ?= 2 | PREFIX ?= /usr/local 3 | MANDIR ?= $(PREFIX)/share/man 4 | 5 | DOXYGEN := $(shell command -v doxygen 2> /dev/null) 6 | DOXYGENFLAGS = 7 | 8 | .PHONY: all 9 | all: 10 | ifndef DOXYGEN 11 | $(warning "doxygen is not available please install it") 12 | else 13 | $(DOXYGEN) $(DOXYGENFLAGS) 14 | endif 15 | 16 | .PHONY: clean 17 | clean: 18 | ifdef DOXYGEN 19 | rm -Rf html 20 | rm -Rf man 21 | endif 22 | 23 | .PHONY: install 24 | install: 25 | ifdef DOXYGEN 26 | install -d $(DESTDIR)$(MANDIR)/man3 27 | install man/man3/libtinyalsa-pcm.3 $(DESTDIR)$(MANDIR)/man3 28 | install man/man3/libtinyalsa-mixer.3 $(DESTDIR)$(MANDIR)/man3 29 | endif 30 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # See debhelper(7) (uncomment to enable) 3 | # output every command that modifies files on the build system. 4 | #DH_VERBOSE = 1 5 | 6 | DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) 7 | export DEB_HOST_MULTIARCH 8 | 9 | PREPROCESS_FILES := $(wildcard debian/*.in) 10 | 11 | $(PREPROCESS_FILES:.in=): %: %.in 12 | sed 's,/@DEB_HOST_MULTIARCH@,$(DEB_HOST_MULTIARCH:%=/%),g' $< > $@ 13 | 14 | .PHONY: override_dh_auto_clean 15 | override_dh_auto_clean: 16 | dh_auto_clean 17 | rm -rf $(PREPROCESS_FILES:.in=) 18 | 19 | .PHONY: override_dh_auto_install 20 | override_dh_auto_install: $(PREPROCESS_FILES:.in=) 21 | dh_auto_install -- PREFIX=/usr 22 | 23 | .PHONY: override_dh_auto_test 24 | override_dh_auto_test: 25 | 26 | 27 | .PHONY: override_dh_strip 28 | override_dh_strip: 29 | dh_strip --dbg-package=libtinyalsa-dbg 30 | 31 | %: 32 | dh $@ 33 | 34 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project ('tinyalsa', 'c', 2 | version : run_command(find_program('scripts/version.sh'), 'print', '-s').stdout().strip(), 3 | meson_version : '>= 0.48.0') 4 | 5 | tinyalsa_includes = include_directories('.', 'include') 6 | 7 | cc = meson.get_compiler('c') 8 | 9 | # Dependency on libdl 10 | dl_dep = cc.find_library('dl') 11 | 12 | tinyalsa = library('tinyalsa', 13 | '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', 14 | include_directories: tinyalsa_includes, 15 | version: meson.project_version(), 16 | install: true, 17 | dependencies: dl_dep) 18 | 19 | # For use as a Meson subproject 20 | tinyalsa_dep = declare_dependency(link_with: tinyalsa, 21 | include_directories: include_directories('include')) 22 | 23 | if not get_option('docs').disabled() 24 | # subdir('docs') # FIXME 25 | endif 26 | 27 | if not get_option('examples').disabled() 28 | subdir('examples') 29 | endif 30 | 31 | subdir('include/tinyalsa') 32 | 33 | if not get_option('utils').disabled() 34 | subdir('utils') 35 | endif 36 | 37 | pkg = import('pkgconfig') 38 | pkg.generate(tinyalsa, description: 'TinyALSA Library') 39 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export DESTDIR ?= 2 | export PREFIX ?= /usr/local 3 | 4 | export INCDIR ?= $(PREFIX)/include/tinyalsa 5 | export LIBDIR ?= $(PREFIX)/lib 6 | export BINDIR ?= $(PREFIX)/bin 7 | export MANDIR ?= $(PREFIX)/share/man 8 | 9 | export VERSIONSCRIPT = $(shell pwd)/scripts/version.sh 10 | 11 | export TINYALSA_VERSION_MAJOR = $(shell $(VERSIONSCRIPT) -s print major) 12 | export TINYALSA_VERSION = $(shell $(VERSIONSCRIPT) -s print ) 13 | 14 | .PHONY: all 15 | all: 16 | $(MAKE) -C src 17 | $(MAKE) -C utils 18 | $(MAKE) -C doxygen 19 | $(MAKE) -C examples 20 | 21 | .PHONY: clean 22 | clean: 23 | $(MAKE) -C src clean 24 | $(MAKE) -C utils clean 25 | $(MAKE) -C doxygen clean 26 | $(MAKE) -C examples clean 27 | 28 | .PHONY: install 29 | install: 30 | install -d $(DESTDIR)$(INCDIR)/ 31 | install include/tinyalsa/attributes.h $(DESTDIR)$(INCDIR)/ 32 | install include/tinyalsa/pcm.h $(DESTDIR)$(INCDIR)/ 33 | install include/tinyalsa/mixer.h $(DESTDIR)$(INCDIR)/ 34 | install include/tinyalsa/asoundlib.h $(DESTDIR)$(INCDIR)/ 35 | install include/tinyalsa/version.h $(DESTDIR)$(INCDIR)/ 36 | install include/tinyalsa/plugin.h $(DESTDIR)$(INCDIR)/ 37 | $(MAKE) -C src install 38 | $(MAKE) -C utils install 39 | $(MAKE) -C doxygen install 40 | 41 | -------------------------------------------------------------------------------- /utils/tinypcminfo.1: -------------------------------------------------------------------------------- 1 | .TH TINYPCMINFO 1 "October 2, 2016" "tinypcminfo" "TinyALSA" 2 | 3 | .SH NAME 4 | tinypcminfo \- prints the hardware parameters of a PCM. 5 | 6 | .SH SYNOPSIS 7 | .B tinypcminfo\fR [ \fIoptions\fR ] 8 | 9 | .SH Description 10 | 11 | \fBtinypcminfo\fR prints the hardware parameters of a PCM, specified by it's card and device number. 12 | 13 | .SH OPTIONS 14 | 15 | .TP 16 | \fB\-D\fR \fIcard\fR 17 | Card number of the PCM. 18 | The default is 0. 19 | 20 | .TP 21 | \fB\-d\fR \fIdevice\fR 22 | Device number of the PCM. 23 | The default is 0. 24 | 25 | .SH EXAMPLES 26 | 27 | .TP 28 | \fBtinypcminfo\fR 29 | Prints hardware parameters for the PCM of card 0 and device 0. 30 | 31 | .TP 32 | \fBtinypcminfo -D 1 33 | Prints hardware parameters for the PCM of card 1 and device 0. 34 | 35 | .TP 36 | \fBtinypcminfo -d 1 37 | Prints hardware parameters for the PCM of card 0 and device 1. 38 | 39 | .TP 40 | \fBtinypcminfo -D 1 -d 1 41 | Prints hardware parameters for the PCM of card 1 and device 1. 42 | 43 | .SH BUGS 44 | 45 | Please report bugs to https://github.com/tinyalsa/tinyalsa/issues. 46 | 47 | .SH SEE ALSO 48 | 49 | .BR tinycap(1), 50 | .BR tinyplay(1), 51 | .BR tinymix(1) 52 | 53 | .SH AUTHORS 54 | Simon Wilson 55 | .P 56 | For a complete list of authors, visit the project page at https://github.com/tinyalsa/tinyalsa. 57 | 58 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: tinyalsa 2 | Section: sound 3 | Priority: optional 4 | Maintainer: Taylor Holberton 5 | Build-Depends: debhelper (>=7.0.50), doxygen 6 | Vcs-Git: git://github.com/tinyalsa/tinyalsa 7 | Vcs-Browser: https://github.com/tinyalsa/tinyalsa 8 | Standards-Version: 3.9.7 9 | 10 | Package: tinyalsa 11 | Architecture: any 12 | Section: sound 13 | Depends: libtinyalsa (= ${binary:Version}), 14 | ${misc:Depends}, 15 | ${shlibs:Depends} 16 | Description: A collection of small programs to interface with ALSA in the Linux kernel. 17 | TinyALSA is a lightweight interface to ALSA in the Linux kernel. 18 | 19 | Package: libtinyalsa 20 | Architecture: any 21 | Multi-Arch: same 22 | Section: libs 23 | Depends: ${misc:Depends}, 24 | ${shlibs:Depends} 25 | Description: A small C library for interfacing with ALSA in the Linux kernel. 26 | The TinyALSA library is a lightweight, bare metal version of the ALSA library. 27 | 28 | Package: libtinyalsa-dev 29 | Architecture: any 30 | Multi-Arch: same 31 | Section: libdevel 32 | Depends: libtinyalsa, 33 | ${misc:Depends}, 34 | ${shlibs:Depends} 35 | Description: Development files for the TinyALSA library. 36 | The TinyALSA library is a lightweight, bare metal version of the ALSA library. 37 | 38 | Package: libtinyalsa-dbg 39 | Architecture: any 40 | Multi-Arch: same 41 | Section: debug 42 | Depends: libtinyalsa, 43 | ${misc:Depends} 44 | Description: Debug files for the TinyALSA library. 45 | The TinyALSA library is a lightweight, bare metal version of the ALSA library. 46 | 47 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2011, The Android Open Source Project 2 | Copyright (c) 2019, The Linux Foundation. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of The Android Open Source Project nor the names of 12 | its contributors may be used to endorse or promote products derived 13 | from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 25 | DAMAGE. 26 | -------------------------------------------------------------------------------- /include/tinyalsa/attributes.h: -------------------------------------------------------------------------------- 1 | #ifndef TINYALSA_ATTRIBUTES_H 2 | #define TINYALSA_ATTRIBUTES_H 3 | 4 | /** @defgroup libtinyalsa-attributes 5 | * @brief GCC attributes to issue diagnostics 6 | * when the library is being used incorrectly. 7 | * */ 8 | 9 | // FIXME: Disable the deprecated attribute in Android temporarily. pcm_read/write are marked as 10 | // deprecated functions in the latest tinyalsa in GitHub. However, there are lots of libraries in 11 | // Android using these functions and building with -Werror flags. Besides build breakage, the 12 | // behavior and interface of the successors, pcm_readi/writei, are also changed. Once all have 13 | // been cleaned up, we will enable this again. 14 | #if defined(__GNUC__) && !defined(ANDROID) 15 | 16 | /** Issues a warning when a function is being 17 | * used that is now deprecated. 18 | * @ingroup libtinyalsa-attributes 19 | * */ 20 | #define TINYALSA_DEPRECATED __attribute__((deprecated)) 21 | 22 | /** Issues a warning when a return code of 23 | * a function is not checked. 24 | * @ingroup libtinyalsa-attributes 25 | * */ 26 | #define TINYALSA_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) 27 | 28 | #else /* __GNUC__ && !ANDROID */ 29 | 30 | /** This is just a placeholder for compilers 31 | * that aren't GCC or Clang. 32 | * @ingroup libtinyalsa-attributes 33 | * */ 34 | #define TINYALSA_DEPRECATED 35 | 36 | /** This is just a placeholder for compilers 37 | * that aren't GCC or Clang. 38 | * @ingroup libtinyalsa-attributes 39 | * */ 40 | #define TINYALSA_WARN_UNUSED_RESULT 41 | 42 | #endif /* __GNUC__ && !ANDROID */ 43 | 44 | #endif /* TINYALSA_ATTRIBUTES_H */ 45 | -------------------------------------------------------------------------------- /utils/Makefile: -------------------------------------------------------------------------------- 1 | DESTDIR ?= 2 | PREFIX ?= /usr/local 3 | BINDIR ?= $(PREFIX)/bin 4 | MANDIR ?= $(PREFIX)/man 5 | 6 | CROSS_COMPILE ?= 7 | CC = $(CROSS_COMPILE)gcc 8 | 9 | CFLAGS += -Wall -Wextra -Werror -Wfatal-errors 10 | CFLAGS += -I ../include 11 | CFLAGS += -fPIC 12 | CFLAGS += -O2 13 | 14 | LDFLAGS += -L ../src 15 | LDFLAGS += -pie 16 | 17 | VPATH = ../src:../include/tinyalsa 18 | 19 | .PHONY: all 20 | all: -ltinyalsa tinyplay tinycap tinymix tinypcminfo 21 | 22 | tinyplay tinycap tinypcminfo tinymix: LDLIBS+=-ldl 23 | 24 | tinyplay: tinyplay.o libtinyalsa.a 25 | 26 | tinyplay.o: tinyplay.c pcm.h mixer.h asoundlib.h optparse.h 27 | 28 | tinycap: tinycap.o libtinyalsa.a 29 | 30 | tinycap.o: tinycap.c pcm.h mixer.h asoundlib.h optparse.h 31 | 32 | tinymix: tinymix.o libtinyalsa.a 33 | 34 | tinymix.o: tinymix.c pcm.h mixer.h asoundlib.h optparse.h 35 | 36 | tinypcminfo: tinypcminfo.o libtinyalsa.a 37 | 38 | tinypcminfo.o: tinypcminfo.c pcm.h mixer.h asoundlib.h optparse.h 39 | 40 | .PHONY: clean 41 | clean: 42 | $(RM) tinyplay tinyplay.o 43 | $(RM) tinycap tinycap.o 44 | $(RM) tinymix tinymix.o 45 | $(RM) tinypcminfo tinypcminfo.o 46 | 47 | .PHONY: install 48 | install: tinyplay tinycap tinymix tinypcminfo 49 | install -d $(DESTDIR)$(BINDIR) 50 | install tinyplay $(DESTDIR)$(BINDIR)/ 51 | install tinycap $(DESTDIR)$(BINDIR)/ 52 | install tinymix $(DESTDIR)$(BINDIR)/ 53 | install tinypcminfo $(DESTDIR)$(BINDIR)/ 54 | install -d $(DESTDIR)$(MANDIR)/man1 55 | install tinyplay.1 $(DESTDIR)$(MANDIR)/man1/ 56 | install tinycap.1 $(DESTDIR)$(MANDIR)/man1/ 57 | install tinymix.1 $(DESTDIR)$(MANDIR)/man1/ 58 | install tinypcminfo.1 $(DESTDIR)$(MANDIR)/man1/ 59 | 60 | -------------------------------------------------------------------------------- /include/tinyalsa/asoundlib.h: -------------------------------------------------------------------------------- 1 | /* asoundlib.h 2 | ** 3 | ** Copyright 2011, The Android Open Source Project 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above copyright 10 | ** notice, this list of conditions and the following disclaimer in the 11 | ** documentation and/or other materials provided with the distribution. 12 | ** * Neither the name of The Android Open Source Project nor the names of 13 | ** its contributors may be used to endorse or promote products derived 14 | ** from this software without specific prior written permission. 15 | ** 16 | ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND 17 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE 20 | ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 26 | ** DAMAGE. 27 | */ 28 | 29 | #ifndef TINYALSA_ASOUNDLIB_H 30 | #define TINYALSA_ASOUNDLIB_H 31 | 32 | #include "mixer.h" 33 | #include "pcm.h" 34 | #include "version.h" 35 | 36 | #endif 37 | 38 | -------------------------------------------------------------------------------- /utils/tinymix.1: -------------------------------------------------------------------------------- 1 | .TH TINYMIX 1 "October 2, 2016" "tinymix" "TinyALSA" 2 | 3 | .SH NAME 4 | tinymix \- view and edit mixer controls for a specified mixer. 5 | 6 | .SH SYNOPSIS 7 | .B tinymix\fR [ \fIoptions\fR ] \fIcommand\fR 8 | 9 | .SH Description 10 | 11 | \fBtinymix\fR can be used to view and/or edit a list of mixer controls for a specified mixer. 12 | 13 | .SH OPTIONS 14 | 15 | .TP 16 | \fB\-D, --card\fR \fIcard\fR 17 | Card number of the mixer. 18 | The default is 0. 19 | 20 | .TP 21 | \fB\-h, --help\fR 22 | Print help contents and exit. 23 | 24 | .TP 25 | \fB\-v, --version\fR 26 | Print the current version of tinymix and exit. 27 | 28 | .SH COMMANDS 29 | 30 | .TP 31 | \fBget \fR 32 | Prints the value of a specified control 33 | 34 | .TP 35 | \fBset \fR 36 | Sets the value of a specified control 37 | 38 | .TP 39 | \fBcontents\fR 40 | Prints the contents of all mixer controls. 41 | 42 | .TP 43 | \fBcontrols\fR 44 | Prints the names and IDs of all mixer controls. 45 | 46 | .SH EXAMPLES 47 | 48 | .TP 49 | \fBtinymix controls\fR 50 | Prints a list of control IDs for the mixer of card 0. 51 | 52 | .TP 53 | \fBtinymix -D 1 controls\fR 54 | Prints a list of control IDs for the mixer of card 1. 55 | 56 | .TP 57 | \fBtinymix get 0\fR 58 | Prints information about control 0. 59 | 60 | .TP 61 | \fBtinymix get "Headphone Playback Volume"\fR 62 | Prints information about a control called "Headphone Playback Volume"\fR 63 | 64 | .TP 65 | \fBtinymix set 0 4\fR 66 | Sets control 0 to the value of 4. 67 | 68 | .TP 69 | \fBtinymix --card 1 set 2 32 70 | Sets control 2 of card 1 to the value of 32. 71 | 72 | .SH BUGS 73 | 74 | Please report bugs to https://github.com/tinyalsa/tinyalsa/issues. 75 | 76 | .SH SEE ALSO 77 | 78 | .BR tinycap(1), 79 | .BR tinyplay(1), 80 | .BR tinypcminfo(1) 81 | 82 | .SH AUTHORS 83 | Simon Wilson 84 | .P 85 | For a complete list of authors, visit the project page at https://github.com/tinyalsa/tinyalsa. 86 | 87 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | tinyalsa (2.0.0) xenial; urgency=medium 2 | 3 | * Miscellaneous bugs fixed. 4 | * PCM plugin support. 5 | * Add CMake build support. 6 | * Add meson build support. 7 | * tinyplay can now read from stdin. 8 | * Improved versioning support for library. 9 | * Improvements to pcm actions (prepare at open time and after overrun, etc.) 10 | * Improvements/fixes to pcm_get_htimestamp(). 11 | * Fixes for the mixer percent functions. 12 | 13 | -- Simon Wilson Mon, 21 Sep 2020 11:27:00 -0600 14 | 15 | tinyalsa (1.1.1) xenial; urgency=medium 16 | 17 | * Fixed some minor bugs 18 | * Added some protection from integer overflow 19 | 20 | -- Taylor Holberton Tue, 23 May 2017 21:21:10 -0800 21 | 22 | tinyalsa (1.1.0) xenial; urgency=medium 23 | 24 | * Finished most of the PCM and mixer API documentation 25 | * Added const specifiers where necessary 26 | * Added pcm_readi() and pcm_writei() 27 | * Deprecated pcm_read() and pcm_write() 28 | * Added mixer_get_num_ctls_by_name() 29 | * Added pcm_get_channels(), pcm_get_rate() and pcm_get_format() 30 | * Made libtinyalsa.so.x a symbol link, using libtinyalsa.so.x.y.z as library name 31 | * Added long option names in tinyplay 32 | * Using amixer-style interface for tinymix 33 | 34 | -- Taylor Holberton Thu, 01 Dec 2016 21:38:12 -0800 35 | 36 | tinyalsa (1.0.2) xenial; urgency=medium 37 | 38 | * Removed install of libtinyalsa.so in package libtinyalsa 39 | 40 | -- Taylor Holberton Sun, 02 Oct 2016 13:46:33 -0400 41 | 42 | tinyalsa (1.0.1) xenial; urgency=medium 43 | 44 | * Added man pages for tinycap, tinyplay, tinymix and tinypcminfo. 45 | * Fixed overwrite of shared library in package libtinyalsa-dev 46 | 47 | -- Taylor Holberton Sun, 02 Oct 2016 12:24:28 -0400 48 | 49 | tinyalsa (1.0.0) xenial; urgency=medium 50 | 51 | * Initial debian release. 52 | 53 | -- Taylor Holberton Sat, 01 Oct 2016 20:31:04 -0400 54 | 55 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | DESTDIR ?= 2 | PREFIX ?= /usr/local 3 | LIBDIR ?= $(PREFIX)/lib 4 | BINDIR ?= $(PREFIX)/bin 5 | ifdef DEB_HOST_MULTIARCH 6 | LIBDIR := $(LIBDIR)/$(DEB_HOST_MULTIARCH) 7 | endif 8 | 9 | CC = $(CROSS_COMPILE)gcc 10 | AR = $(CROSS_COMPILE)ar 11 | LD = $(CROSS_COMPILE)gcc 12 | 13 | WARNINGS = -Wall -Wextra -Werror -Wfatal-errors 14 | INCLUDE_DIRS = -I ../include 15 | override CFLAGS := $(WARNINGS) $(INCLUDE_DIRS) -fPIC $(CFLAGS) 16 | 17 | VPATH = ../include/tinyalsa 18 | OBJECTS = limits.o mixer.o pcm.o pcm_plugin.o pcm_hw.o snd_card_plugin.o mixer_plugin.o mixer_hw.o 19 | 20 | LIBVERSION_MAJOR = $(TINYALSA_VERSION_MAJOR) 21 | LIBVERSION = $(TINYALSA_VERSION) 22 | 23 | .PHONY: all 24 | all: libtinyalsa.a libtinyalsa.so 25 | 26 | pcm.o: pcm.c limits.h pcm.h pcm_io.h plugin.h snd_card_plugin.h 27 | 28 | pcm_plugin.o: pcm_plugin.c asoundlib.h pcm_io.h plugin.h snd_card_plugin.h 29 | 30 | pcm_hw.o: pcm_hw.c asoundlib.h pcm_io.h 31 | 32 | limits.o: limits.c limits.h 33 | 34 | mixer.o: mixer.c mixer.h mixer_io.h plugin.h 35 | 36 | snd_card_plugin.o: snd_card_plugin.c plugin.h snd_card_plugin.h 37 | 38 | mixer_plugin.o: mixer_plugin.c mixer_io.h plugin.h snd_card_plugin.h 39 | 40 | mixer_hw.o: mixer_hw.c mixer_io.h 41 | 42 | libtinyalsa.a: $(OBJECTS) 43 | $(AR) $(ARFLAGS) $@ $^ 44 | 45 | libtinyalsa.so: libtinyalsa.so.$(LIBVERSION_MAJOR) 46 | ln -sf $< $@ 47 | 48 | libtinyalsa.so.$(LIBVERSION_MAJOR): libtinyalsa.so.$(LIBVERSION) 49 | ln -sf $< $@ 50 | 51 | libtinyalsa.so.$(LIBVERSION): $(OBJECTS) 52 | $(LD) $(LDFLAGS) -shared -Wl,-soname,libtinyalsa.so.$(LIBVERSION_MAJOR) $^ -o $@ 53 | 54 | .PHONY: clean 55 | clean: 56 | rm -f libtinyalsa.a 57 | rm -f libtinyalsa.so 58 | rm -f libtinyalsa.so.$(LIBVERSION_MAJOR) 59 | rm -f libtinyalsa.so.$(LIBVERSION) 60 | rm -f $(OBJECTS) 61 | 62 | .PHONY: install 63 | install: libtinyalsa.a libtinyalsa.so.$(LIBVERSION_MAJOR) 64 | install -d $(DESTDIR)$(LIBDIR)/ 65 | install libtinyalsa.a $(DESTDIR)$(LIBDIR)/ 66 | install libtinyalsa.so.$(LIBVERSION) $(DESTDIR)$(LIBDIR)/ 67 | ln -sf libtinyalsa.so.$(LIBVERSION) $(DESTDIR)$(LIBDIR)/libtinyalsa.so.$(LIBVERSION_MAJOR) 68 | ln -sf libtinyalsa.so.$(LIBVERSION_MAJOR) $(DESTDIR)$(LIBDIR)/libtinyalsa.so 69 | 70 | -------------------------------------------------------------------------------- /examples/pcm-readi.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | static size_t read_frames(void **frames) 7 | { 8 | unsigned int card = 0; 9 | unsigned int device = 0; 10 | int flags = PCM_IN; 11 | 12 | const struct pcm_config config = { 13 | .channels = 2, 14 | .rate = 48000, 15 | .format = PCM_FORMAT_S32_LE, 16 | .period_size = 1024, 17 | .period_count = 2, 18 | .start_threshold = 1024, 19 | .silence_threshold = 1024 * 2, 20 | .stop_threshold = 1024 * 2 21 | }; 22 | 23 | struct pcm *pcm = pcm_open(card, device, flags, &config); 24 | if (pcm == NULL) { 25 | fprintf(stderr, "failed to allocate memory for PCM\n"); 26 | return 0; 27 | } else if (!pcm_is_ready(pcm)){ 28 | pcm_close(pcm); 29 | fprintf(stderr, "failed to open PCM\n"); 30 | return 0; 31 | } 32 | 33 | unsigned int frame_size = pcm_frames_to_bytes(pcm, 1); 34 | unsigned int frames_per_sec = pcm_get_rate(pcm); 35 | 36 | *frames = malloc(frame_size * frames_per_sec); 37 | if (*frames == NULL) { 38 | fprintf(stderr, "failed to allocate frames\n"); 39 | pcm_close(pcm); 40 | return 0; 41 | } 42 | 43 | int read_count = pcm_readi(pcm, *frames, frames_per_sec); 44 | 45 | size_t byte_count = pcm_frames_to_bytes(pcm, read_count); 46 | 47 | pcm_close(pcm); 48 | 49 | return byte_count; 50 | } 51 | 52 | static int write_file(const void *frames, size_t size) 53 | { 54 | FILE *output_file = fopen("audio.raw", "wb"); 55 | if (output_file == NULL) { 56 | perror("failed to open 'audio.raw' for writing"); 57 | return EXIT_FAILURE; 58 | } 59 | fwrite(frames, 1, size, output_file); 60 | fclose(output_file); 61 | return 0; 62 | } 63 | 64 | int main(void) 65 | { 66 | void *frames = NULL; 67 | size_t size = 0; 68 | 69 | size = read_frames(&frames); 70 | if (size == 0) { 71 | return EXIT_FAILURE; 72 | } 73 | 74 | if (write_file(frames, size) < 0) { 75 | free(frames); 76 | return EXIT_FAILURE; 77 | } 78 | 79 | free(frames); 80 | 81 | return EXIT_SUCCESS; 82 | } 83 | 84 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: tinyalsa 3 | Source: https://github.com/tinyalsa/tinyalsa 4 | 5 | Files: * 6 | Copyright: 2016 Simon Wilson 7 | License: BSD-3-Clause 8 | 9 | Files: debian/* 10 | Copyright: 2016 Taylor Holberton 11 | License: BSD-3-Clause 12 | 13 | License: BSD-3-Clause 14 | Redistribution and use in source and binary forms, with or without 15 | modification, are permitted provided that the following conditions 16 | are met: 17 | 1. Redistributions of source code must retain the above copyright 18 | notice, this list of conditions and the following disclaimer. 19 | 2. Redistributions in binary form must reproduce the above copyright 20 | notice, this list of conditions and the following disclaimer in the 21 | documentation and/or other materials provided with the distribution. 22 | 3. Neither the name of the Android Open Source Project nor the names of 23 | its contributors may be used to endorse or promote products derived 24 | from this software without specific prior written permission. 25 | . 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 29 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HOLDERS OR 30 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 31 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 32 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 33 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 34 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 35 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 36 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | 38 | # Please also look if there are files or directories which have a 39 | # different copyright/license attached and list them here. 40 | # Please avoid picking licenses with terms that are more restrictive than the 41 | # packaged work, as it may make Debian's contributions unacceptable upstream. 42 | -------------------------------------------------------------------------------- /utils/tinycap.1: -------------------------------------------------------------------------------- 1 | .TH TINYCAP 1 "October 2, 2016" "tinycap" "TinyALSA" 2 | 3 | .SH NAME 4 | tinycap \- captures audio from an audio device 5 | 6 | .SH SYNOPSIS 7 | .B tinycap\fR [ \fIfile\fR ] [ \fIoptions\fR ] 8 | 9 | .SH Description 10 | 11 | \fBtinycap\fR can record audio from an audio device to a wav file or standard output (as raw samples). 12 | Options can be used to specify various hardware parameters to open the PCM with. 13 | 14 | .SH OPTIONS 15 | 16 | .TP 17 | \fB\-D\fR \fIcard\fR 18 | Card number of the PCM. 19 | The default is 0. 20 | 21 | .TP 22 | \fB\-d\fR \fIdevice\fR 23 | Device number of the PCM. 24 | The default is 0. 25 | 26 | .TP 27 | \fB\-M\fR 28 | Use memory-mapped I/O method. 29 | If this option is not specified, then read and write I/O method will be used. 30 | 31 | .TP 32 | \fB\-c\fR \fIchannels\fR 33 | Number of channels the PCM will have. 34 | The default is 2. 35 | 36 | .TP 37 | \fB\-r\fR \fIrate\fR 38 | Number of frames per second of the PCM. 39 | The default is 48000. 40 | 41 | .TP 42 | \fB\-b\fR \fIbits\fR 43 | Number of bits per sample the PCM will have. 44 | The default is 32. 45 | 46 | .TP 47 | \fB\-p\fR \fIperiod_size\fR 48 | Number of frames in a period. 49 | The default is 1024. 50 | 51 | .TP 52 | \fB\-n\fR \fIperiods\fR 53 | Number of periods the PCM will have. 54 | The default is 4. 55 | 56 | .TP 57 | \fB\-t\fR \fIseconds\fR 58 | Number of seconds to record audio. 59 | 60 | .SH SIGNALS 61 | 62 | When capturing audio, SIGINT will stop the recording and close the file. 63 | 64 | .SH EXAMPLES 65 | 66 | .TP 67 | \fBtinycap output.wav\fR 68 | Records a file called output.wav until an interrupt signal is caught. 69 | 70 | .TP 71 | \fBtinycap output.wav -D 1 -t 2 72 | Records a file called output.wav from card 1 for two seconds or until an interrupt signal is caught. 73 | 74 | .TP 75 | \fBtinycap -- -t 3 76 | Records to standard output for three seconds or until an interrupt signal is caught. 77 | 78 | .SH BUGS 79 | 80 | Please report bugs to https://github.com/tinyalsa/tinyalsa/issues. 81 | 82 | .SH SEE ALSO 83 | 84 | .BR tinyplay(1), 85 | .BR tinymix(1), 86 | .BR tinypcminfo(1) 87 | 88 | .SH AUTHORS 89 | Simon Wilson 90 | .P 91 | For a complete list of authors, visit the project page at https://github.com/tinyalsa/tinyalsa. 92 | 93 | -------------------------------------------------------------------------------- /BUILD: -------------------------------------------------------------------------------- 1 | # BUILD 2 | # 3 | # Copyright 2020, The Android Open Source Project 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # * Redistributions of source code must retain the above copyright 8 | # notice, this list of conditions and the following disclaimer. 9 | # * Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # * Neither the name of The Android Open Source Project nor the names of 13 | # its contributors may be used to endorse or promote products derived 14 | # from this software without specific prior written permission. 15 | # 16 | # THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND 17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | # ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE 20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 26 | # DAMAGE. 27 | # 28 | 29 | cc_library( 30 | name = "tinyalsa", 31 | srcs = glob(["src/*.c"]), 32 | includes = ["include"], 33 | hdrs = glob([ 34 | "include/**/*.h", 35 | "src/*.h", 36 | ]), 37 | visibility = ["//visibility:public"], 38 | ) 39 | 40 | cc_test( 41 | name = "tinyalsa_tests", 42 | srcs = glob([ 43 | "tests/src/*.cc", 44 | "tests/include/*.h", 45 | ]), 46 | includes = ["tests/include"], 47 | deps = [ 48 | "//:tinyalsa", 49 | "@googletest//:gtest_main" 50 | ], 51 | linkopts = [ 52 | "-ldl", 53 | ], 54 | copts = [ 55 | "-std=c++17", 56 | ], 57 | ) 58 | -------------------------------------------------------------------------------- /include/tinyalsa/interval.h: -------------------------------------------------------------------------------- 1 | /* interval.h 2 | ** 3 | ** Copyright 2011, The Android Open Source Project 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above copyright 10 | ** notice, this list of conditions and the following disclaimer in the 11 | ** documentation and/or other materials provided with the distribution. 12 | ** * Neither the name of The Android Open Source Project nor the names of 13 | ** its contributors may be used to endorse or promote products derived 14 | ** from this software without specific prior written permission. 15 | ** 16 | ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND 17 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE 20 | ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 26 | ** DAMAGE. 27 | */ 28 | 29 | #ifndef TINYALSA_INTERVAL_H 30 | #define TINYALSA_INTERVAL_H 31 | 32 | #include 33 | #include 34 | 35 | /** A closed range signed interval. */ 36 | 37 | struct tinyalsa_signed_interval { 38 | /** The maximum value of the interval */ 39 | ssize_t max; 40 | /** The minimum value of the interval */ 41 | ssize_t min; 42 | }; 43 | 44 | /** A closed range unsigned interval. */ 45 | 46 | struct tinyalsa_unsigned_interval { 47 | /** The maximum value of the interval */ 48 | size_t max; 49 | /** The minimum value of the interval */ 50 | size_t min; 51 | }; 52 | 53 | #endif /* TINYALSA_INTERVAL_H */ 54 | 55 | -------------------------------------------------------------------------------- /src/mixer_io.h: -------------------------------------------------------------------------------- 1 | /* mixer_io.h 2 | ** Copyright (c) 2019, The Linux Foundation. 3 | ** 4 | ** Redistribution and use in source and binary forms, with or without 5 | ** modification, are permitted provided that the following conditions are 6 | ** met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above 10 | ** copyright notice, this list of conditions and the following 11 | ** disclaimer in the documentation and/or other materials provided 12 | ** with the distribution. 13 | ** * Neither the name of The Linux Foundation nor the names of its 14 | ** contributors may be used to endorse or promote products derived 15 | ** from this software without specific prior written permission. 16 | ** 17 | ** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED 18 | ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT 20 | ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | ** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 24 | ** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 | ** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 26 | ** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 27 | ** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | **/ 29 | 30 | #ifndef TINYALSA_SRC_MIXER_H 31 | #define TINYALSA_SRC_MIXER_H 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | struct mixer_ops; 38 | 39 | int mixer_hw_open(unsigned int card, void **data, 40 | const struct mixer_ops **ops); 41 | int mixer_plugin_open(unsigned int card, void **data, 42 | const struct mixer_ops **ops); 43 | 44 | struct mixer_ops { 45 | void (*close) (void *data); 46 | int (*get_poll_fd) (void *data, struct pollfd *pfd, int count); 47 | ssize_t (*read_event) (void *data, struct snd_ctl_event *ev, size_t size); 48 | int (*ioctl) (void *data, unsigned int cmd, ...); 49 | }; 50 | 51 | #endif /* TINYALSA_SRC_MIXER_H */ 52 | -------------------------------------------------------------------------------- /src/pcm_io.h: -------------------------------------------------------------------------------- 1 | /* pcm_io.h 2 | ** Copyright (c) 2019, The Linux Foundation. 3 | ** 4 | ** Redistribution and use in source and binary forms, with or without 5 | ** modification, are permitted provided that the following conditions are 6 | ** met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above 10 | ** copyright notice, this list of conditions and the following 11 | ** disclaimer in the documentation and/or other materials provided 12 | ** with the distribution. 13 | ** * Neither the name of The Linux Foundation nor the names of its 14 | ** contributors may be used to endorse or promote products derived 15 | ** from this software without specific prior written permission. 16 | ** 17 | ** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED 18 | ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT 20 | ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | ** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 24 | ** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 | ** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 26 | ** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 27 | ** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | **/ 29 | 30 | #ifndef TINYALSA_SRC_PCM_IO_H 31 | #define TINYALSA_SRC_PCM_IO_H 32 | 33 | #include 34 | #include 35 | 36 | struct snd_node; 37 | 38 | struct pcm_ops { 39 | int (*open) (unsigned int card, unsigned int device, 40 | unsigned int flags, void **data, struct snd_node *node); 41 | void (*close) (void *data); 42 | int (*ioctl) (void *data, unsigned int cmd, ...); 43 | void *(*mmap) (void *data, void *addr, size_t length, int prot, int flags, 44 | off_t offset); 45 | int (*munmap) (void *data, void *addr, size_t length); 46 | int (*poll) (void *data, struct pollfd *pfd, nfds_t nfds, int timeout); 47 | }; 48 | 49 | extern const struct pcm_ops hw_ops; 50 | extern const struct pcm_ops plug_ops; 51 | 52 | #endif /* TINYALSA_SRC_PCM_IO_H */ 53 | -------------------------------------------------------------------------------- /utils/tinyplay.1: -------------------------------------------------------------------------------- 1 | .TH TINYPLAY 1 "October 2, 2016" "tinyplay" "TinyALSA" 2 | 3 | .SH NAME 4 | tinyplay \- sends audio to an audio device 5 | 6 | .SH SYNOPSIS 7 | .B tinyplay\fR \fIfile\fR [ \fIoptions\fR ] 8 | 9 | .SH Description 10 | 11 | \fBtinyplay\fR can send audio to an audio device from a wav file or standard input (as raw samples). 12 | Options can be used to specify various hardware parameters to open the PCM with. 13 | 14 | .SH OPTIONS 15 | 16 | .TP 17 | \fB\-D, --card\fR \fIcard\fR 18 | Card number of the PCM. 19 | The default is 0. 20 | 21 | .TP 22 | \fB\-d, --device\fR \fIdevice\fR 23 | Device number of the PCM. 24 | The default is 0. 25 | 26 | .TP 27 | \fB\-c, --channels\fR \fIchannels\fR 28 | Number of channels the PCM will have. 29 | This option is only valid for raw file types. 30 | The default is 2 for raw file types. 31 | 32 | .TP 33 | \fB\-r, --rate\fR \fIrate\fR 34 | Number of frames per second of the PCM. 35 | This option is only valid for raw file types. 36 | The default is 48000 for raw file types. 37 | 38 | .TP 39 | \fB\-i, --file-type\fR \fIfile-type\fR 40 | The file type used for playback. 41 | Available types are \fIraw\fR and \fIwav\fR. 42 | Specifying \fIraw\fR means that \fIchannels\fR, \fIrate\fR and \fIbits\fR may have to be specified as well. 43 | By default, the file type is determined by the file name. 44 | Specifying the file type with this option will take precedent over the one determined by the file name. 45 | 46 | .TP 47 | \fB\-b, --bits\fR \fIbits\fR 48 | Number of bits per sample the PCM will have. 49 | This option is only valid for raw file types. 50 | The default is 16 for raw file types. 51 | 52 | .TP 53 | \fB\-p, --period-size\fR \fIperiod_size\fR 54 | Number of frames in a period. 55 | The default is 1024. 56 | 57 | .TP 58 | \fB\-n, --period-count\fR \fIperiods\fR 59 | Number of periods the PCM will have. 60 | The default is 4. 61 | 62 | .SH SIGNALS 63 | 64 | When playing audio, SIGINT will stop the playback and close the file. 65 | 66 | .SH EXAMPLES 67 | 68 | .TP 69 | \fBtinyplay output.wav\fR 70 | Plays a file called output.wav. 71 | 72 | .TP 73 | \fBtinyplay output.wav -D 1 74 | Plays a file called output.wav on card 1. 75 | 76 | .TP 77 | \fBtinyplay output.raw -i raw --channels 2 --rate 44100 --bits 32 78 | Plays a raw audio file called output.raw; using 2 channels, 44100 frames per second and 32 bits per sample. 79 | 80 | .SH BUGS 81 | 82 | Please report bugs to https://github.com/tinyalsa/tinyalsa/issues. 83 | 84 | .SH SEE ALSO 85 | 86 | .BR tinycap(1), 87 | .BR tinymix(1), 88 | .BR tinypcminfo(1) 89 | 90 | .SH AUTHORS 91 | Simon Wilson 92 | .P 93 | For a complete list of authors, visit the project page at https://github.com/tinyalsa/tinyalsa. 94 | 95 | -------------------------------------------------------------------------------- /examples/pcm-writei.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | static long int file_size(FILE * file) 7 | { 8 | if (fseek(file, 0, SEEK_END) < 0) { 9 | return -1; 10 | } 11 | long int file_size = ftell(file); 12 | if (fseek(file, 0, SEEK_SET) < 0) { 13 | return -1; 14 | } 15 | return file_size; 16 | } 17 | 18 | static size_t read_file(void ** frames){ 19 | 20 | FILE * input_file = fopen("audio.raw", "rb"); 21 | if (input_file == NULL) { 22 | perror("failed to open 'audio.raw' for writing"); 23 | return 0; 24 | } 25 | 26 | long int size = file_size(input_file); 27 | if (size < 0) { 28 | perror("failed to get file size of 'audio.raw'"); 29 | fclose(input_file); 30 | return 0; 31 | } 32 | 33 | *frames = malloc(size); 34 | if (*frames == NULL) { 35 | fprintf(stderr, "failed to allocate frames\n"); 36 | fclose(input_file); 37 | return 0; 38 | } 39 | 40 | size = fread(*frames, 1, size, input_file); 41 | 42 | fclose(input_file); 43 | 44 | return size; 45 | } 46 | 47 | static int write_frames(const void * frames, size_t byte_count){ 48 | 49 | unsigned int card = 0; 50 | unsigned int device = 0; 51 | int flags = PCM_OUT; 52 | 53 | const struct pcm_config config = { 54 | .channels = 2, 55 | .rate = 48000, 56 | .format = PCM_FORMAT_S32_LE, 57 | .period_size = 1024, 58 | .period_count = 2, 59 | .start_threshold = 1024, 60 | .silence_threshold = 1024 * 2, 61 | .stop_threshold = 1024 * 2 62 | }; 63 | 64 | struct pcm * pcm = pcm_open(card, device, flags, &config); 65 | if (pcm == NULL) { 66 | fprintf(stderr, "failed to allocate memory for PCM\n"); 67 | return -1; 68 | } else if (!pcm_is_ready(pcm)){ 69 | pcm_close(pcm); 70 | fprintf(stderr, "failed to open PCM\n"); 71 | return -1; 72 | } 73 | 74 | unsigned int frame_count = pcm_bytes_to_frames(pcm, byte_count); 75 | 76 | int err = pcm_writei(pcm, frames, frame_count); 77 | if (err < 0) { 78 | printf("error: %s\n", pcm_get_error(pcm)); 79 | } 80 | 81 | pcm_close(pcm); 82 | 83 | return 0; 84 | } 85 | 86 | int main(void) 87 | { 88 | void *frames; 89 | size_t size; 90 | 91 | size = read_file(&frames); 92 | if (size == 0) { 93 | return EXIT_FAILURE; 94 | } 95 | 96 | if (write_frames(frames, size) < 0) { 97 | return EXIT_FAILURE; 98 | } 99 | 100 | free(frames); 101 | return EXIT_SUCCESS; 102 | } 103 | 104 | -------------------------------------------------------------------------------- /include/tinyalsa/limits.h: -------------------------------------------------------------------------------- 1 | /* limits.h 2 | ** 3 | ** Copyright 2011, The Android Open Source Project 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above copyright 10 | ** notice, this list of conditions and the following disclaimer in the 11 | ** documentation and/or other materials provided with the distribution. 12 | ** * Neither the name of The Android Open Source Project nor the names of 13 | ** its contributors may be used to endorse or promote products derived 14 | ** from this software without specific prior written permission. 15 | ** 16 | ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND 17 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE 20 | ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 26 | ** DAMAGE. 27 | */ 28 | 29 | #ifndef TINYALSA_LIMITS_H 30 | #define TINYALSA_LIMITS_H 31 | 32 | #include 33 | 34 | #include 35 | #include 36 | 37 | #define TINYALSA_SIGNED_INTERVAL_MAX SSIZE_MAX 38 | #define TINYALSA_SIGNED_INTERVAL_MIN SSIZE_MIN 39 | 40 | #define TINYALSA_UNSIGNED_INTERVAL_MAX SIZE_MAX 41 | #define TINYALSA_UNSIGNED_INTERVAL_MIN SIZE_MIN 42 | 43 | #define TINYALSA_CHANNELS_MAX 32U 44 | #define TINYALSA_CHANNELS_MIN 1U 45 | 46 | #define TINYALSA_FRAMES_MAX (ULONG_MAX / (TINYALSA_CHANNELS_MAX * 4)) 47 | #define TINYALSA_FRAMES_MIN 0U 48 | 49 | #if TINYALSA_FRAMES_MAX > TINYALSA_UNSIGNED_INTERVAL_MAX 50 | #error "Frames max exceeds measurable value." 51 | #endif 52 | 53 | #if TINYALSA_FRAMES_MIN < TINYALSA_UNSIGNED_INTERVAL_MIN 54 | #error "Frames min exceeds measurable value." 55 | #endif 56 | 57 | extern const struct tinyalsa_unsigned_interval tinyalsa_channels_limit; 58 | 59 | extern const struct tinyalsa_unsigned_interval tinyalsa_frames_limit; 60 | 61 | #endif /* TINYALSA_LIMITS_H */ 62 | 63 | -------------------------------------------------------------------------------- /include/tinyalsa/version.h: -------------------------------------------------------------------------------- 1 | /* version.h 2 | ** 3 | ** Copyright 2011, The Android Open Source Project 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above copyright 10 | ** notice, this list of conditions and the following disclaimer in the 11 | ** documentation and/or other materials provided with the distribution. 12 | ** * Neither the name of The Android Open Source Project nor the names of 13 | ** its contributors may be used to endorse or promote products derived 14 | ** from this software without specific prior written permission. 15 | ** 16 | ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND 17 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE 20 | ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 26 | ** DAMAGE. 27 | */ 28 | 29 | #ifndef TINYALSA_VERSION_H 30 | #define TINYALSA_VERSION_H 31 | 32 | /* Macros for expanding the version numbers into string literals */ 33 | #define TINYALSA_VERSION_STR_EX(number) #number 34 | #define TINYALSA_VERSION_STR(number) TINYALSA_VERSION_STR_EX (number) 35 | 36 | #define TINYALSA_VERSION_MAJOR 2 37 | 38 | #define TINYALSA_VERSION_MINOR 0 39 | 40 | #define TINYALSA_VERSION_PATCH 0 41 | 42 | /* The final version number is constructed based on minor, major and patch */ 43 | #define TINYALSA_VERSION \ 44 | ((unsigned long) \ 45 | ((TINYALSA_VERSION_MAJOR << 16) | \ 46 | (TINYALSA_VERSION_MINOR << 8 ) | \ 47 | (TINYALSA_VERSION_PATCH ))) 48 | 49 | /* The version string is constructed by concatenating individual ver. strings */ 50 | #define TINYALSA_VERSION_STRING \ 51 | TINYALSA_VERSION_STR (TINYALSA_VERSION_MAJOR) \ 52 | "." \ 53 | TINYALSA_VERSION_STR (TINYALSA_VERSION_MINOR) \ 54 | "." \ 55 | TINYALSA_VERSION_STR (TINYALSA_VERSION_PATCH) 56 | 57 | #endif /* TINYALSA_VERSION_H */ 58 | 59 | -------------------------------------------------------------------------------- /src/snd_card_plugin.h: -------------------------------------------------------------------------------- 1 | /* snd_utils.h 2 | ** Copyright (c) 2019, The Linux Foundation. 3 | ** 4 | ** Redistribution and use in source and binary forms, with or without 5 | ** modification, are permitted provided that the following conditions are 6 | ** met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above 10 | ** copyright notice, this list of conditions and the following 11 | ** disclaimer in the documentation and/or other materials provided 12 | ** with the distribution. 13 | ** * Neither the name of The Linux Foundation nor the names of its 14 | ** contributors may be used to endorse or promote products derived 15 | ** from this software without specific prior written permission. 16 | ** 17 | ** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED 18 | ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT 20 | ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | ** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 24 | ** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 | ** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 26 | ** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 27 | ** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | **/ 29 | 30 | #ifndef TINYALSA_SRC_SND_CARD_UTILS_H 31 | #define TINYALSA_SRC_SND_CARD_UTILS_H 32 | 33 | #include 34 | 35 | #include 36 | 37 | /** Encapsulates the pcm device definition from 38 | * the sound card definition configuration file. 39 | */ 40 | struct snd_node { 41 | /** Pointer the card definition */ 42 | void *card_node; 43 | /** Pointer to device definition, either PCM or MIXER device */ 44 | void *dev_node; 45 | /** Pointer to the sound card parser library */ 46 | void *dl_hdl; 47 | /** A pointer to the operations structure. */ 48 | const struct snd_node_ops* ops; 49 | }; 50 | 51 | enum snd_node_type { 52 | SND_NODE_TYPE_HW = 0, 53 | SND_NODE_TYPE_PLUGIN, 54 | SND_NODE_TYPE_INVALID, 55 | }; 56 | 57 | enum { 58 | NODE_PCM, 59 | NODE_MIXER 60 | }; 61 | 62 | struct snd_node *snd_utils_open_pcm(unsigned int card, unsigned int device); 63 | 64 | struct snd_node *snd_utils_open_mixer(unsigned int card); 65 | 66 | void snd_utils_close_dev_node(struct snd_node *node); 67 | 68 | enum snd_node_type snd_utils_get_node_type(struct snd_node *node); 69 | 70 | int snd_utils_get_int(struct snd_node *node, const char *prop, int *val); 71 | 72 | int snd_utils_get_str(struct snd_node *node, const char *prop, char **val); 73 | 74 | #endif /* end of TINYALSA_SRC_SND_CARD_UTILS_H */ 75 | -------------------------------------------------------------------------------- /Android.bp: -------------------------------------------------------------------------------- 1 | package { 2 | default_applicable_licenses: ["external_tinyalsa_new_license"], 3 | } 4 | 5 | // Added automatically by a large-scale-change that took the approach of 6 | // 'apply every license found to every target'. While this makes sure we respect 7 | // every license restriction, it may not be entirely correct. 8 | // 9 | // e.g. GPL in an MIT project might only apply to the contrib/ directory. 10 | // 11 | // Please consider splitting the single license below into multiple licenses, 12 | // taking care not to lose any license_kind information, and overriding the 13 | // default license using the 'licenses: [...]' property on targets as needed. 14 | // 15 | // For unused files, consider creating a 'fileGroup' with "//visibility:private" 16 | // to attach the license to, and including a comment whether the files may be 17 | // used in the current project. 18 | // See: http://go/android-license-faq 19 | license { 20 | name: "external_tinyalsa_new_license", 21 | visibility: [":__subpackages__"], 22 | license_kinds: [ 23 | "SPDX-license-identifier-BSD", 24 | "SPDX-license-identifier-Unlicense", 25 | ], 26 | license_text: [ 27 | "NOTICE", 28 | ], 29 | } 30 | 31 | cc_library { 32 | name: "libtinyalsav2", 33 | host_supported: true, 34 | vendor_available: true, 35 | srcs: [ 36 | "src/mixer.c", 37 | "src/mixer_hw.c", 38 | "src/mixer_plugin.c", 39 | "src/pcm.c", 40 | "src/pcm_hw.c", 41 | "src/pcm_plugin.c", 42 | "src/snd_card_plugin.c", 43 | ], 44 | cflags: ["-Werror", "-Wno-macro-redefined"], 45 | export_include_dirs: ["include"], 46 | local_include_dirs: ["include"], 47 | 48 | target: { 49 | darwin: { 50 | enabled: false, 51 | }, 52 | }, 53 | 54 | system_shared_libs: ["libc", "libdl"], 55 | 56 | sanitize: { 57 | integer_overflow: true, 58 | misc_undefined: ["bounds"], 59 | diag: { 60 | integer_overflow: true, 61 | misc_undefined: ["bounds"], 62 | }, 63 | }, 64 | } 65 | 66 | cc_library_headers { 67 | name: "libtinyalsav2_headers", 68 | export_include_dirs: ["include"], 69 | vendor_available: true, 70 | } 71 | 72 | cc_binary { 73 | name: "tinyplay2", 74 | host_supported: true, 75 | srcs: ["utils/tinyplay.c"], 76 | static_libs: ["libtinyalsav2"], 77 | cflags: ["-Werror"], 78 | target: { 79 | darwin: { 80 | enabled: false, 81 | }, 82 | }, 83 | } 84 | 85 | cc_binary { 86 | name: "tinycap2", 87 | srcs: ["utils/tinycap.c"], 88 | static_libs: ["libtinyalsav2"], 89 | cflags: ["-Werror"], 90 | } 91 | 92 | cc_binary { 93 | name: "tinymix2", 94 | srcs: ["utils/tinymix.c"], 95 | static_libs: ["libtinyalsav2"], 96 | cflags: ["-Werror", "-Wall"], 97 | } 98 | 99 | cc_binary { 100 | name: "tinypcminfo2", 101 | srcs: ["utils/tinypcminfo.c"], 102 | static_libs: ["libtinyalsav2"], 103 | cflags: ["-Werror"], 104 | } 105 | -------------------------------------------------------------------------------- /tests/include/pcm_test_device.h: -------------------------------------------------------------------------------- 1 | /* pcm_test.h 2 | ** 3 | ** Copyright 2020, The Android Open Source Project 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above copyright 10 | ** notice, this list of conditions and the following disclaimer in the 11 | ** documentation and/or other materials provided with the distribution. 12 | ** * Neither the name of The Android Open Source Project nor the names of 13 | ** its contributors may be used to endorse or promote products derived 14 | ** from this software without specific prior written permission. 15 | ** 16 | ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND 17 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE 20 | ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 26 | ** DAMAGE. 27 | */ 28 | 29 | #ifndef TINYALSA_TESTS_PCM_TEST_H_ 30 | #define TINYALSA_TESTS_PCM_TEST_H_ 31 | 32 | #include "tinyalsa/pcm.h" 33 | 34 | namespace tinyalsa { 35 | namespace testing { 36 | 37 | #ifndef TEST_LOOPBACK_CARD 38 | #define TEST_LOOPBACK_CARD 2 39 | #endif 40 | 41 | #ifndef TEST_LOOPBACK_PLAYBACK_DEVICE 42 | #define TEST_LOOPBACK_PLAYBACK_DEVICE 0 43 | #endif 44 | 45 | #ifndef TEST_LOOPBACK_CAPTURE_DEVICE 46 | #define TEST_LOOPBACK_CAPTURE_DEVICE 1 47 | #endif 48 | 49 | static constexpr unsigned int kLoopbackCard = TEST_LOOPBACK_CARD; 50 | static constexpr unsigned int kLoopbackPlaybackDevice = TEST_LOOPBACK_PLAYBACK_DEVICE; 51 | static constexpr unsigned int kLoopbackCaptureDevice = TEST_LOOPBACK_CAPTURE_DEVICE; 52 | 53 | static constexpr unsigned int kDefaultChannels = 2; 54 | static constexpr unsigned int kDefaultSamplingRate = 48000; 55 | static constexpr unsigned int kDefaultPeriodSize = 1024; 56 | static constexpr unsigned int kDefaultPeriodCount = 3; 57 | static constexpr pcm_config kDefaultConfig = { 58 | .channels = kDefaultChannels, 59 | .rate = kDefaultSamplingRate, 60 | .period_size = kDefaultPeriodSize, 61 | .period_count = kDefaultPeriodCount, 62 | .format = PCM_FORMAT_S16_LE, 63 | .start_threshold = kDefaultPeriodSize, 64 | .stop_threshold = kDefaultPeriodSize * kDefaultPeriodCount, 65 | .silence_threshold = 0, 66 | .silence_size = 0, 67 | }; 68 | 69 | } // namespace testing 70 | } // namespace tinyalsa 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/scripts/version.sh -s print 4 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 5 | OUTPUT_VARIABLE TINYALSA_VERSION) 6 | 7 | project("TinyALSA" VERSION ${TINYALSA_VERSION} LANGUAGES C) 8 | 9 | set(CMAKE_C_STANDARD 99) 10 | set(CMAKE_C_STANDARD_REQUIRED ON) 11 | set(CMAKE_C_EXTENSIONS OFF) 12 | 13 | # Options 14 | option(BUILD_SHARED_LIBS "Build shared libraries" ON) 15 | option(TINYALSA_USES_PLUGINS "Whether or not to build with plugin support" OFF) 16 | option(TINYALSA_BUILD_EXAMPLES "Build examples" ON) 17 | option(TINYALSA_BUILD_UTILS "Build utility tools" ON) 18 | 19 | # Library 20 | add_library("tinyalsa" 21 | "src/pcm.c" 22 | "src/pcm_hw.c" 23 | "src/pcm_plugin.c" 24 | "src/snd_card_plugin.c" 25 | "src/mixer.c" 26 | "src/mixer_hw.c" 27 | "src/mixer_plugin.c") 28 | 29 | set_property(TARGET "tinyalsa" PROPERTY PUBLIC_HEADER 30 | "include/tinyalsa/attributes.h" 31 | "include/tinyalsa/version.h" 32 | "include/tinyalsa/asoundlib.h" 33 | "include/tinyalsa/pcm.h" 34 | "include/tinyalsa/plugin.h" 35 | "include/tinyalsa/mixer.h") 36 | 37 | set_target_properties("tinyalsa" PROPERTIES 38 | VERSION ${TinyALSA_VERSION} 39 | SOVERSION ${TinyALSA_VERSION_MAJOR}) 40 | 41 | target_include_directories("tinyalsa" PUBLIC 42 | $ 43 | $) 44 | target_compile_definitions("tinyalsa" PRIVATE 45 | $<$:TINYALSA_USES_PLUGINS> 46 | PUBLIC _POSIX_C_SOURCE=200809L) 47 | target_link_libraries("tinyalsa" PUBLIC ${CMAKE_DL_LIBS}) 48 | 49 | # Examples 50 | if(TINYALSA_BUILD_EXAMPLES) 51 | set(TINYALSA_EXAMPLES pcm-readi pcm-writei) 52 | else() 53 | set(TINYALSA_EXAMPLES) 54 | endif() 55 | 56 | foreach(EXAMPLE IN LISTS TINYALSA_EXAMPLES) 57 | add_executable("${EXAMPLE}" "examples/${EXAMPLE}.c") 58 | target_link_libraries("${EXAMPLE}" PRIVATE "tinyalsa") 59 | endforeach() 60 | 61 | # Utilities 62 | if(TINYALSA_BUILD_UTILS) 63 | set(TINYALSA_UTILS tinyplay tinycap tinypcminfo tinymix tinywavinfo) 64 | else() 65 | set(TINYALSA_UTILS) 66 | endif() 67 | 68 | foreach(UTIL IN LISTS TINYALSA_UTILS) 69 | add_executable("${UTIL}" "utils/${UTIL}.c") 70 | target_link_libraries("${UTIL}" PRIVATE "tinyalsa") 71 | endforeach() 72 | 73 | if(TINYALSA_BUILD_UTILS) 74 | target_link_libraries("tinywavinfo" PRIVATE m) 75 | endif() 76 | 77 | # Add C warning flags 78 | include(CheckCCompilerFlag) 79 | foreach(FLAG IN ITEMS -Wall -Wextra -Wpedantic -Werror -Wfatal-errors) 80 | string(TOUPPER "HAVE${FLAG}" HAVE_VAR) 81 | string(REPLACE "-" "_" HAVE_VAR "${HAVE_VAR}") 82 | check_c_compiler_flag("${FLAG}" "${HAVE_VAR}") 83 | if("${${HAVE_VAR}}") 84 | target_compile_options("tinyalsa" PRIVATE "${FLAG}") 85 | foreach(UTIL IN LISTS TINYALSA_UTILS) 86 | target_compile_options("${UTIL}" PRIVATE "${FLAG}") 87 | endforeach() 88 | endif() 89 | endforeach() 90 | 91 | # Install 92 | include(GNUInstallDirs) 93 | install(TARGETS "tinyalsa" ${TINYALSA_UTILS} 94 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 95 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 96 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 97 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tinyalsa) 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TinyALSA 2 | ======== 3 | 4 | [![Build Status](https://travis-ci.org/tinyalsa/tinyalsa.svg?branch=master)](https://travis-ci.org/tinyalsa/tinyalsa) 5 | 6 | TinyALSA is a small library to interface with ALSA in the Linux kernel. 7 | 8 | The aims are: 9 | 10 | - Provide a basic pcm and mixer API. 11 | - If it's not absolutely needed, don't add it to the API. 12 | - Avoid supporting complex and unnecessary operations, that could be 13 | dealt with at a higher level. 14 | - Provide comprehensive documentation. 15 | 16 | ### Building 17 | 18 | TinyALSA supports these build systems: 19 | 20 | - [CMake](https://en.wikipedia.org/wiki/CMake) 21 | - [Make](https://en.wikipedia.org/wiki/Make_(software)) 22 | - [Meson](https://en.wikipedia.org/wiki/Meson_(software)) 23 | - [Soong](https://android.googlesource.com/platform/build/soong/+/refs/heads/master/README.md) for Android 24 | 25 | To build and install with Make, run the commands: 26 | 27 | ``` 28 | make 29 | sudo make install 30 | sudo ldconfig 31 | ``` 32 | 33 | ### Installing 34 | 35 | TinyALSA is now available as a set of the following [Debian](https://en.wikipedia.org/wiki/Debian) 36 | packages from [launchpad](https://launchpad.net/~taylorcholberton/+archive/ubuntu/tinyalsa): 37 | 38 | | Package Name: | Description: | 39 | |-----------------|-----------------------------------------------------| 40 | | tinyalsa | Contains tinyplay, tinycap, tinymix and tinypcminfo | 41 | | libtinyalsa | Contains the shared library | 42 | | libtinyalsa-dev | Contains the static library and header files | 43 | 44 | To install these packages, run the commands: 45 | 46 | ``` 47 | sudo apt-add-repository ppa:taylorcholberton/tinyalsa 48 | sudo apt-get update 49 | sudo apt-get install tinyalsa 50 | sudo apt-get install libtinyalsa-dev 51 | ``` 52 | 53 | ### Documentation 54 | 55 | Once installed, the man pages are available via: 56 | 57 | ``` 58 | man tinyplay 59 | man tinycap 60 | man tinymix 61 | man tinypcminfo 62 | man libtinyalsa-pcm 63 | man libtinyalsa-mixer 64 | ``` 65 | 66 | ### Test 67 | 68 | To test libtinyalsa, please follow the instructions, 69 | 70 | #### Setup Bazel build environment 71 | 72 | Visit [here](https://docs.bazel.build/versions/3.7.0/install.html) to get more info to setup Bazel environment. 73 | 74 | #### Insert loopback devices 75 | 76 | The test program does pcm_* operations on loopback devices. You have to insert loopback devices after your system boots up. 77 | 78 | ``` 79 | sudo modprobe snd-aloop 80 | sudo chmod 777 /dev/snd/* 81 | ``` 82 | 83 | #### Run test program 84 | 85 | ``` 86 | bazel test //:tinyalsa_tests --test_output=all 87 | ``` 88 | 89 | 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. 90 | 91 | ``` 92 | bazel test //:tinyalsa_tests --test_output=all \ 93 | --copt=-DTEST_LOOPBACK_CARD=[loopback card] \ 94 | --copt=-DTEST_LOOPBACK_PLAYBACK_DEVICE=[loopback playback device] \ 95 | --copt=-DTEST_LOOPBACK_CAPTURE_DEVICE=[loopback capture device] 96 | ``` 97 | 98 | #### Generate coverage report 99 | 100 | ``` 101 | bazel coverage //:tinyalsa_tests --combined_report=lcov --test_output=all 102 | genhtml bazel-out/_coverage/_coverage_report.dat -o tinyalsa_tests_coverage 103 | ``` 104 | -------------------------------------------------------------------------------- /src/mixer_hw.c: -------------------------------------------------------------------------------- 1 | /* mixer_hw.c 2 | ** Copyright (c) 2019, The Linux Foundation. 3 | ** 4 | ** Redistribution and use in source and binary forms, with or without 5 | ** modification, are permitted provided that the following conditions are 6 | ** met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above 10 | ** copyright notice, this list of conditions and the following 11 | ** disclaimer in the documentation and/or other materials provided 12 | ** with the distribution. 13 | ** * Neither the name of The Linux Foundation nor the names of its 14 | ** contributors may be used to endorse or promote products derived 15 | ** from this software without specific prior written permission. 16 | ** 17 | ** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED 18 | ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT 20 | ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | ** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 24 | ** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 | ** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 26 | ** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 27 | ** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | **/ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include 43 | 44 | #include 45 | #include 46 | #include 47 | 48 | #include "mixer_io.h" 49 | 50 | /** Store the hardware (kernel interface) mixer data */ 51 | struct mixer_hw_data { 52 | /* Card number for the mixer */ 53 | unsigned int card; 54 | /* File descriptor of the mixer device node */ 55 | int fd; 56 | }; 57 | 58 | static void mixer_hw_close(void *data) 59 | { 60 | struct mixer_hw_data *hw_data = data; 61 | 62 | if (!hw_data) 63 | return; 64 | 65 | if (hw_data->fd >= 0) 66 | close(hw_data->fd); 67 | 68 | hw_data->fd = -1; 69 | free(hw_data); 70 | hw_data = NULL; 71 | } 72 | 73 | static int mixer_hw_ioctl(void *data, unsigned int cmd, ...) 74 | { 75 | struct mixer_hw_data *hw_data = data; 76 | va_list ap; 77 | void *arg; 78 | 79 | va_start(ap, cmd); 80 | arg = va_arg(ap, void *); 81 | va_end(ap); 82 | 83 | return ioctl(hw_data->fd, cmd, arg); 84 | } 85 | 86 | static ssize_t mixer_hw_read_event(void *data, struct snd_ctl_event *ev, 87 | size_t size) 88 | { 89 | struct mixer_hw_data *hw_data = data; 90 | 91 | return read(hw_data->fd, ev, size); 92 | } 93 | 94 | static const struct mixer_ops mixer_hw_ops = { 95 | .close = mixer_hw_close, 96 | .ioctl = mixer_hw_ioctl, 97 | .read_event = mixer_hw_read_event, 98 | }; 99 | 100 | int mixer_hw_open(unsigned int card, void **data, 101 | const struct mixer_ops **ops) 102 | { 103 | struct mixer_hw_data *hw_data; 104 | int fd; 105 | char fn[256]; 106 | 107 | snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card); 108 | fd = open(fn, O_RDWR); 109 | if (fd < 0) 110 | return fd; 111 | 112 | hw_data = calloc(1, sizeof(*hw_data)); 113 | if (!hw_data) 114 | return -1; 115 | 116 | hw_data->card = card; 117 | hw_data->fd = fd; 118 | *data = hw_data; 119 | *ops = &mixer_hw_ops; 120 | 121 | return fd; 122 | } 123 | -------------------------------------------------------------------------------- /tests/src/pcm_in_test.cc: -------------------------------------------------------------------------------- 1 | /* pcm_in_test.c 2 | ** 3 | ** Copyright 2020, The Android Open Source Project 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above copyright 10 | ** notice, this list of conditions and the following disclaimer in the 11 | ** documentation and/or other materials provided with the distribution. 12 | ** * Neither the name of The Android Open Source Project nor the names of 13 | ** its contributors may be used to endorse or promote products derived 14 | ** from this software without specific prior written permission. 15 | ** 16 | ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND 17 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE 20 | ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 26 | ** DAMAGE. 27 | */ 28 | #include "pcm_test_device.h" 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | #include "tinyalsa/pcm.h" 37 | 38 | namespace tinyalsa { 39 | namespace testing { 40 | 41 | class PcmInTest : public ::testing::Test { 42 | protected: 43 | PcmInTest() : pcm_object(nullptr) {} 44 | virtual ~PcmInTest() = default; 45 | 46 | virtual void SetUp() override { 47 | pcm_object = pcm_open(kLoopbackCard, kLoopbackCaptureDevice, PCM_IN, &kDefaultConfig); 48 | ASSERT_NE(pcm_object, nullptr); 49 | ASSERT_TRUE(pcm_is_ready(pcm_object)); 50 | } 51 | 52 | virtual void TearDown() override { 53 | ASSERT_EQ(pcm_close(pcm_object), 0); 54 | } 55 | 56 | static constexpr unsigned int kDefaultChannels = 2; 57 | static constexpr unsigned int kDefaultSamplingRate = 48000; 58 | static constexpr unsigned int kDefaultPeriodSize = 1024; 59 | static constexpr unsigned int kDefaultPeriodCount = 3; 60 | static constexpr pcm_config kDefaultConfig = { 61 | .channels = kDefaultChannels, 62 | .rate = kDefaultSamplingRate, 63 | .period_size = kDefaultPeriodSize, 64 | .period_count = kDefaultPeriodCount, 65 | .format = PCM_FORMAT_S16_LE, 66 | .start_threshold = 0, 67 | .stop_threshold = 0, 68 | .silence_threshold = 0, 69 | .silence_size = 0, 70 | }; 71 | 72 | pcm* pcm_object; 73 | }; 74 | 75 | TEST_F(PcmInTest, GetDelay) { 76 | pcm_prepare(pcm_object); 77 | long delay = pcm_get_delay(pcm_object); 78 | std::cout << delay << std::endl; 79 | ASSERT_GE(delay, 0); 80 | } 81 | 82 | TEST_F(PcmInTest, Readi) { 83 | constexpr uint32_t read_count = 20; 84 | 85 | size_t buffer_size = pcm_frames_to_bytes(pcm_object, kDefaultConfig.period_size); 86 | auto buffer = std::make_unique(buffer_size); 87 | 88 | int read_frames = 0; 89 | unsigned int frames = pcm_bytes_to_frames(pcm_object, buffer_size); 90 | auto start = std::chrono::steady_clock::now(); 91 | for (uint32_t i = 0; i < read_count; ++i) { 92 | read_frames = pcm_readi(pcm_object, buffer.get(), frames); 93 | ASSERT_EQ(read_frames, frames); 94 | } 95 | 96 | std::chrono::duration difference = std::chrono::steady_clock::now() - start; 97 | std::chrono::milliseconds expected_elapsed_time_ms(frames * read_count / 98 | (kDefaultConfig.rate / 1000)); 99 | 100 | std::cout << difference.count() << std::endl; 101 | std::cout << expected_elapsed_time_ms.count() << std::endl; 102 | 103 | ASSERT_NEAR(difference.count() * 1000, expected_elapsed_time_ms.count(), 100); 104 | } 105 | 106 | TEST_F(PcmInTest, Writei) { 107 | size_t buffer_size = pcm_frames_to_bytes(pcm_object, kDefaultConfig.period_size); 108 | auto buffer = std::make_unique(buffer_size); 109 | 110 | unsigned int frames = pcm_bytes_to_frames(pcm_object, buffer_size); 111 | ASSERT_EQ(pcm_writei(pcm_object, buffer.get(), frames), -EINVAL); 112 | } 113 | 114 | } // namespace testing 115 | } // namespace tinyalsa 116 | -------------------------------------------------------------------------------- /src/snd_card_plugin.c: -------------------------------------------------------------------------------- 1 | /* snd_card_plugin.c 2 | ** Copyright (c) 2019, The Linux Foundation. 3 | ** 4 | ** Redistribution and use in source and binary forms, with or without 5 | ** modification, are permitted provided that the following conditions are 6 | ** met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above 10 | ** copyright notice, this list of conditions and the following 11 | ** disclaimer in the documentation and/or other materials provided 12 | ** with the distribution. 13 | ** * Neither the name of The Linux Foundation nor the names of its 14 | ** contributors may be used to endorse or promote products derived 15 | ** from this software without specific prior written permission. 16 | ** 17 | ** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED 18 | ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT 20 | ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | ** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 24 | ** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 | ** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 26 | ** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 27 | ** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | **/ 29 | 30 | #include "snd_card_plugin.h" 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | #define SND_DLSYM(h, p, s, err) \ 37 | do { \ 38 | err = 0; \ 39 | p = dlsym(h, s); \ 40 | if (!p) \ 41 | err = -ENODEV; \ 42 | } while(0) 43 | 44 | int snd_utils_get_int(struct snd_node *node, const char *prop, int *val) 45 | { 46 | if (!node || !node->card_node || !node->dev_node) 47 | return SND_NODE_TYPE_HW; 48 | 49 | return node->ops->get_int(node->dev_node, prop, val); 50 | } 51 | 52 | int snd_utils_get_str(struct snd_node *node, const char *prop, char **val) 53 | { 54 | if (!node || !node->card_node || !node->dev_node) 55 | return SND_NODE_TYPE_HW; 56 | 57 | return node->ops->get_str(node->dev_node, prop, val); 58 | } 59 | 60 | void snd_utils_close_dev_node(struct snd_node *node) 61 | { 62 | if (!node) 63 | return; 64 | 65 | if (node->card_node) 66 | node->ops->close_card(node->card_node); 67 | 68 | if (node->dl_hdl) 69 | dlclose(node->dl_hdl); 70 | 71 | free(node); 72 | } 73 | 74 | enum snd_node_type snd_utils_get_node_type(struct snd_node *node) 75 | { 76 | int val = SND_NODE_TYPE_HW; 77 | 78 | if (!node || !node->card_node || !node->dev_node) 79 | return SND_NODE_TYPE_HW; 80 | 81 | node->ops->get_int(node->dev_node, "type", &val); 82 | 83 | return val; 84 | } 85 | 86 | static int snd_utils_resolve_symbols(struct snd_node *node) 87 | { 88 | void *dl = node->dl_hdl; 89 | int err; 90 | SND_DLSYM(dl, node->ops, "snd_card_ops", err); 91 | return err; 92 | } 93 | 94 | static struct snd_node *snd_utils_open_dev_node(unsigned int card, 95 | unsigned int device, 96 | int dev_type) 97 | { 98 | struct snd_node *node; 99 | int rc = 0; 100 | 101 | node = calloc(1, sizeof(*node)); 102 | if (!node) 103 | return NULL; 104 | 105 | node->dl_hdl = dlopen("libsndcardparser.so", RTLD_NOW); 106 | if (!node->dl_hdl) { 107 | goto err_dl_open; 108 | } 109 | 110 | rc = snd_utils_resolve_symbols(node); 111 | if (rc < 0) 112 | goto err_resolve_symbols; 113 | 114 | node->card_node = node->ops->open_card(card); 115 | if (!node->card_node) 116 | goto err_resolve_symbols; 117 | 118 | if (dev_type == NODE_PCM) { 119 | node->dev_node = node->ops->get_pcm(node->card_node, device); 120 | } else { 121 | node->dev_node = node->ops->get_mixer(node->card_node); 122 | } 123 | 124 | if (!node->dev_node) 125 | goto err_get_node; 126 | 127 | return node; 128 | 129 | err_get_node: 130 | node->ops->close_card(node->card_node); 131 | 132 | err_resolve_symbols: 133 | dlclose(node->dl_hdl); 134 | 135 | err_dl_open: 136 | free(node); 137 | return NULL; 138 | } 139 | 140 | struct snd_node* snd_utils_open_pcm(unsigned int card, 141 | unsigned int device) 142 | { 143 | return snd_utils_open_dev_node(card, device, NODE_PCM); 144 | } 145 | 146 | struct snd_node* snd_utils_open_mixer(unsigned int card) 147 | { 148 | return snd_utils_open_dev_node(card, 0, NODE_MIXER); 149 | } 150 | -------------------------------------------------------------------------------- /src/pcm_hw.c: -------------------------------------------------------------------------------- 1 | /* pcm_hw.c 2 | ** 3 | ** Copyright (c) 2019, The Linux Foundation. 4 | ** Copyright 2021, The Android Open Source Project 5 | ** 6 | ** Redistribution and use in source and binary forms, with or without 7 | ** modification, are permitted provided that the following conditions are 8 | ** met: 9 | ** * Redistributions of source code must retain the above copyright 10 | ** notice, this list of conditions and the following disclaimer. 11 | ** * Redistributions in binary form must reproduce the above 12 | ** copyright notice, this list of conditions and the following 13 | ** disclaimer in the documentation and/or other materials provided 14 | ** with the distribution. 15 | ** * Neither the name of The Linux Foundation nor the names of its 16 | ** contributors may be used to endorse or promote products derived 17 | ** from this software without specific prior written permission. 18 | ** 19 | ** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED 20 | ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 21 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT 22 | ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 23 | ** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 26 | ** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 27 | ** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 28 | ** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 29 | ** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | **/ 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | #include "pcm_io.h" 49 | 50 | struct pcm_hw_data { 51 | /** Card number of the pcm device */ 52 | unsigned int card; 53 | /** Device number for the pcm device */ 54 | unsigned int device; 55 | /** File descriptor to the pcm device file node */ 56 | int fd; 57 | /** Pointer to the pcm node from snd card definiton */ 58 | struct snd_node *node; 59 | }; 60 | 61 | static void pcm_hw_close(void *data) 62 | { 63 | struct pcm_hw_data *hw_data = data; 64 | 65 | if (hw_data->fd >= 0) 66 | close(hw_data->fd); 67 | 68 | free(hw_data); 69 | } 70 | 71 | static int pcm_hw_ioctl(void *data, unsigned int cmd, ...) 72 | { 73 | struct pcm_hw_data *hw_data = data; 74 | va_list ap; 75 | void *arg; 76 | 77 | va_start(ap, cmd); 78 | arg = va_arg(ap, void *); 79 | va_end(ap); 80 | 81 | return ioctl(hw_data->fd, cmd, arg); 82 | } 83 | 84 | static int pcm_hw_poll(void *data __attribute__((unused)), 85 | struct pollfd *pfd, nfds_t nfds, int timeout) 86 | { 87 | return poll(pfd, nfds, timeout); 88 | } 89 | 90 | static void *pcm_hw_mmap(void *data, void *addr, size_t length, int prot, 91 | int flags, off_t offset) 92 | { 93 | struct pcm_hw_data *hw_data = data; 94 | 95 | return mmap(addr, length, prot, flags, hw_data->fd, offset); 96 | } 97 | 98 | static int pcm_hw_munmap(void *data __attribute__((unused)), void *addr, size_t length) 99 | { 100 | return munmap(addr, length); 101 | } 102 | 103 | static int pcm_hw_open(unsigned int card, unsigned int device, 104 | unsigned int flags, void **data, struct snd_node *node) 105 | { 106 | struct pcm_hw_data *hw_data; 107 | char fn[256]; 108 | int fd; 109 | 110 | hw_data = calloc(1, sizeof(*hw_data)); 111 | if (!hw_data) { 112 | return -ENOMEM; 113 | } 114 | 115 | snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, 116 | flags & PCM_IN ? 'c' : 'p'); 117 | // Open the device with non-blocking flag to avoid to be blocked in kernel when all of the 118 | // substreams of this PCM device are opened by others. 119 | fd = open(fn, O_RDWR | O_NONBLOCK); 120 | 121 | if (fd < 0) { 122 | free(hw_data); 123 | return fd; 124 | } 125 | 126 | if ((flags & PCM_NONBLOCK) == 0) { 127 | // Set the file descriptor to blocking mode. 128 | if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK) < 0) { 129 | fprintf(stderr, "failed to set to blocking mode on %s", fn); 130 | close(fd); 131 | free(hw_data); 132 | return -ENODEV; 133 | } 134 | } 135 | 136 | hw_data->card = card; 137 | hw_data->device = device; 138 | hw_data->fd = fd; 139 | hw_data->node = node; 140 | 141 | *data = hw_data; 142 | 143 | return fd; 144 | } 145 | 146 | const struct pcm_ops hw_ops = { 147 | .open = pcm_hw_open, 148 | .close = pcm_hw_close, 149 | .ioctl = pcm_hw_ioctl, 150 | .mmap = pcm_hw_mmap, 151 | .munmap = pcm_hw_munmap, 152 | .poll = pcm_hw_poll, 153 | }; 154 | 155 | -------------------------------------------------------------------------------- /include/tinyalsa/mixer.h: -------------------------------------------------------------------------------- 1 | /* mixer.h 2 | ** 3 | ** Copyright 2011, The Android Open Source Project 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above copyright 10 | ** notice, this list of conditions and the following disclaimer in the 11 | ** documentation and/or other materials provided with the distribution. 12 | ** * Neither the name of The Android Open Source Project nor the names of 13 | ** its contributors may be used to endorse or promote products derived 14 | ** from this software without specific prior written permission. 15 | ** 16 | ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND 17 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE 20 | ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 26 | ** DAMAGE. 27 | */ 28 | 29 | /** @file */ 30 | 31 | /** @defgroup libtinyalsa-mixer Mixer Interface 32 | * @brief All macros, structures and functions that make up the mixer interface. 33 | */ 34 | 35 | #ifndef TINYALSA_MIXER_H 36 | #define TINYALSA_MIXER_H 37 | 38 | #include 39 | #include 40 | 41 | #if defined(__cplusplus) 42 | extern "C" { 43 | #endif 44 | 45 | struct mixer; 46 | 47 | struct mixer_ctl; 48 | 49 | // mixer_ctl_event is a mirroring structure of snd_ctl_event 50 | struct mixer_ctl_event { 51 | int type; 52 | union { 53 | struct { 54 | unsigned int mask; 55 | struct { 56 | unsigned int numid; 57 | int iface; 58 | unsigned int device; 59 | unsigned int subdevice; 60 | unsigned char name[44]; 61 | unsigned int index; 62 | } id; 63 | } element; 64 | unsigned char data[60]; 65 | } data; 66 | }; 67 | 68 | /** Mixer control type. 69 | * @ingroup libtinyalsa-mixer 70 | */ 71 | enum mixer_ctl_type { 72 | /** boolean control type */ 73 | MIXER_CTL_TYPE_BOOL, 74 | /** integer control type */ 75 | MIXER_CTL_TYPE_INT, 76 | /** an enumerated control type */ 77 | MIXER_CTL_TYPE_ENUM, 78 | MIXER_CTL_TYPE_BYTE, 79 | MIXER_CTL_TYPE_IEC958, 80 | /** a 64 bit integer control type */ 81 | MIXER_CTL_TYPE_INT64, 82 | /** unknown control type */ 83 | MIXER_CTL_TYPE_UNKNOWN, 84 | /** end of the enumeration (not a control type) */ 85 | MIXER_CTL_TYPE_MAX, 86 | }; 87 | 88 | struct mixer *mixer_open(unsigned int card); 89 | 90 | void mixer_close(struct mixer *mixer); 91 | 92 | int mixer_add_new_ctls(struct mixer *mixer); 93 | 94 | const char *mixer_get_name(const struct mixer *mixer); 95 | 96 | unsigned int mixer_get_num_ctls(const struct mixer *mixer); 97 | 98 | unsigned int mixer_get_num_ctls_by_name(const struct mixer *mixer, const char *name); 99 | 100 | const struct mixer_ctl *mixer_get_ctl_const(const struct mixer *mixer, unsigned int id); 101 | 102 | struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id); 103 | 104 | struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name); 105 | 106 | struct mixer_ctl *mixer_get_ctl_by_name_and_device(struct mixer *mixer, 107 | const char *name, 108 | unsigned int device); 109 | 110 | struct mixer_ctl *mixer_get_ctl_by_name_and_index(struct mixer *mixer, 111 | const char *name, 112 | unsigned int index); 113 | 114 | int mixer_subscribe_events(struct mixer *mixer, int subscribe); 115 | 116 | int mixer_wait_event(struct mixer *mixer, int timeout); 117 | 118 | unsigned int mixer_ctl_get_id(const struct mixer_ctl *ctl); 119 | 120 | const char *mixer_ctl_get_name(const struct mixer_ctl *ctl); 121 | 122 | enum mixer_ctl_type mixer_ctl_get_type(const struct mixer_ctl *ctl); 123 | 124 | const char *mixer_ctl_get_type_string(const struct mixer_ctl *ctl); 125 | 126 | unsigned int mixer_ctl_get_num_values(const struct mixer_ctl *ctl); 127 | 128 | unsigned int mixer_ctl_get_num_enums(const struct mixer_ctl *ctl); 129 | 130 | const char *mixer_ctl_get_enum_string(struct mixer_ctl *ctl, unsigned int enum_id); 131 | 132 | /* Some sound cards update their controls due to external events, 133 | * such as HDMI EDID byte data changing when an HDMI cable is 134 | * connected. This API allows the count of elements to be updated. 135 | */ 136 | void mixer_ctl_update(struct mixer_ctl *ctl); 137 | 138 | int mixer_ctl_is_access_tlv_rw(const struct mixer_ctl *ctl); 139 | 140 | /* Set and get mixer controls */ 141 | int mixer_ctl_get_percent(const struct mixer_ctl *ctl, unsigned int id); 142 | 143 | int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent); 144 | 145 | int mixer_ctl_get_value(const struct mixer_ctl *ctl, unsigned int id); 146 | 147 | int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count); 148 | 149 | int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value); 150 | 151 | int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count); 152 | 153 | int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string); 154 | 155 | /* Determine range of integer mixer controls */ 156 | int mixer_ctl_get_range_min(const struct mixer_ctl *ctl); 157 | 158 | int mixer_ctl_get_range_max(const struct mixer_ctl *ctl); 159 | 160 | unsigned int mixer_ctl_get_device(const struct mixer_ctl *ctl); 161 | 162 | int mixer_read_event(struct mixer *mixer, struct mixer_ctl_event *event); 163 | 164 | int mixer_consume_event(struct mixer *mixer); 165 | #if defined(__cplusplus) 166 | } /* extern "C" */ 167 | #endif 168 | 169 | #endif 170 | 171 | -------------------------------------------------------------------------------- /scripts/version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 4 | # 5 | # Project configuration variables 6 | # 7 | # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 8 | VERSION_FILE="include/tinyalsa/version.h" 9 | CHANGELOG_FILE="debian/changelog" 10 | 11 | # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 12 | # 13 | # Scripts internal variables 14 | # 15 | # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 16 | LF="\n" 17 | PARAMS="" 18 | DRYRUN=0 19 | 20 | # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 21 | # 22 | # Helper functions 23 | # 24 | # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 25 | die() 26 | { 27 | echo "Error: $@" 1>&2 28 | exit 1 29 | } 30 | 31 | print_usage() 32 | { 33 | echo 34 | echo "Usage: $0 [OPTIONS] ACTION" 35 | echo 36 | echo "Available options:" 37 | echo " -s,--script Format output in \"script\" mode (no trailing newline)." 38 | echo " -d,--dry-run Does not commit anything to any file, just prints." 39 | echo 40 | echo "Available actions:" 41 | echo " print [minor|major|patch] Print the current version." 42 | echo " release [minor|major|patch] Bump the specified version part" 43 | echo " check Check the changelog latest released" 44 | echo " version against the version file." 45 | echo 46 | echo "Please run this script from the project root folder." 47 | echo 48 | } 49 | 50 | check_files() 51 | { 52 | [ -f ${VERSION_FILE} ] || die "No ${VERSION_FILE} found!"; 53 | [ -f ${CHANGELOG_FILE} ] || die "No ${CHANGELOG_FILE} found!" 54 | } 55 | 56 | # Gets a part of the version from the project version file (version.h). 57 | # Takes one argument: the matching version identifier in the version file, e.g. 58 | # TINYALSA_VERSION_MAJOR 59 | get_version_part() 60 | { 61 | set -- "$1" "$(grep -m 1 "^#define\([ \t]*\)$1" ${VERSION_FILE} | sed 's/[^0-9]*//g')" 62 | 63 | if [ -z "$2" ]; then 64 | die "Could not get $1 from ${VERSION_FILE}" 65 | fi 66 | 67 | echo "$2" 68 | } 69 | 70 | 71 | # Gets the complete version from the version file. 72 | # Sets VERSION_MAJOR, VERSION_MINOR and VERSION_PATCH globals 73 | get_version() 74 | { 75 | VERSION_MAJOR=$(get_version_part "TINYALSA_VERSION_MAJOR") 76 | VERSION_MINOR=$(get_version_part "TINYALSA_VERSION_MINOR") 77 | VERSION_PATCH=$(get_version_part "TINYALSA_VERSION_PATCH") 78 | } 79 | 80 | # Commits the new version part to the version file. 81 | # Takes two arguments: the version part identifier in the version file and the 82 | # new version number. If no arguments, do nothing. 83 | commit_version_part() 84 | { 85 | if [ -z $1 ] || [ -z $2 ]; then 86 | return 0 87 | fi 88 | 89 | sed -i "s/\(^#define[ \t]*$1\)[ \t]*\([0-9]*\)/\1 $2/g" ${VERSION_FILE} \ 90 | || die "Could not commit version for $1"; 91 | 92 | [ $(get_version_part $1) = "$2" ] || die "Version check after commit failed for $1" 93 | 94 | return 0; 95 | } 96 | 97 | # Commits the new version to the version file. 98 | # Takes three arguments, the new version numbers for major, minor and patch 99 | commit_version() 100 | { 101 | commit_version_part "TINYALSA_VERSION_PATCH" $1 102 | commit_version_part "TINYALSA_VERSION_MINOR" $2 103 | commit_version_part "TINYALSA_VERSION_MAJOR" $3 104 | 105 | return 0 106 | } 107 | 108 | # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 109 | # 110 | # Actions implementations / functions 111 | # 112 | # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 113 | print_version() 114 | { 115 | if [ -z $1 ]; then 116 | printf "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}${LF}" 117 | else 118 | case "$1" in 119 | major) 120 | printf "${VERSION_MAJOR}${LF}" 121 | ;; 122 | minor) 123 | printf "${VERSION_MINOR}${LF}" 124 | ;; 125 | patch) 126 | printf "${VERSION_PATCH}${LF}" 127 | ;; 128 | *) 129 | die "Unknown part \"$1\" (must be one of minor, major and patch)." 130 | ;; 131 | esac 132 | fi 133 | 134 | return 0 135 | } 136 | 137 | bump_version() 138 | { 139 | case "${1:-patch}" in 140 | major) 141 | VERSION_MAJOR=$((VERSION_MAJOR+1)) 142 | VERSION_MINOR=0 143 | VERSION_PATCH=0 144 | ;; 145 | minor) 146 | VERSION_MINOR=$((VERSION_MINOR+1)) 147 | VERSION_PATCH=0 148 | ;; 149 | patch) 150 | VERSION_PATCH=$((VERSION_PATCH+1)) 151 | ;; 152 | *) 153 | die "Unknown part \"$1\" (must be one of minor, major and patch)." 154 | ;; 155 | esac 156 | 157 | if [ ${DRYRUN} -ne 1 ]; then 158 | commit_version ${VERSION_PATCH} ${VERSION_MINOR} ${VERSION_MAJOR} 159 | fi 160 | 161 | print_version 162 | return 0 163 | } 164 | 165 | check_version() 166 | { 167 | # set $1 to log version, and $2 to ref version 168 | set -- \ 169 | "$(grep -m 1 "^tinyalsa (" ${CHANGELOG_FILE}| sed "s/[^0-9.]*//g")" \ 170 | "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}" 171 | 172 | if [ "$1" != "$2" ]; then 173 | die "Changelog version ($1) does not match package version ($2)." 174 | fi 175 | 176 | printf "Changelog version ($1) OK!${LF}" 177 | return 0 178 | } 179 | 180 | # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 181 | # 182 | # Command Line parsing 183 | # 184 | # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 185 | parse_command() 186 | { 187 | if [ "$#" -eq "0" ]; then 188 | print_usage 189 | exit 1 190 | fi 191 | 192 | case "$1" in 193 | print) 194 | get_version 195 | print_version "$2" 196 | exit $? 197 | ;; 198 | release) 199 | get_version 200 | bump_version "$2" 201 | exit $? 202 | ;; 203 | check) 204 | get_version 205 | check_version 206 | exit $? 207 | ;; 208 | *) 209 | die "Unsupported action \"$1\"." 210 | ;; 211 | esac 212 | } 213 | 214 | # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 215 | # 216 | # Main 217 | # 218 | # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 219 | 220 | set -e 221 | trap "set +e" 0 222 | 223 | # Checking parameters 224 | if [ "$#" -eq "0" ]; then 225 | print_usage 226 | exit 0 227 | fi 228 | 229 | while [ "$#" -ne "0" ]; do 230 | case "$1" in 231 | -s|--script) 232 | unset LF 233 | shift 234 | ;; 235 | -d|--dry-run) 236 | DRYRUN=1 237 | shift 238 | ;; 239 | --) 240 | shift 241 | break 242 | ;; 243 | -*|--*=) 244 | die "Unsupported flag \"$1\"." 245 | ;; 246 | *) 247 | PARAMS="$PARAMS ${1}" 248 | shift 249 | ;; 250 | esac 251 | done 252 | 253 | # set positional arguments in their proper place 254 | set -- "${PARAMS}" 255 | 256 | check_files 257 | parse_command ${PARAMS} 258 | 259 | # The script should never reach this place. 260 | die "Internal error. Please report this." 261 | -------------------------------------------------------------------------------- /tests/src/pcm_test.cc: -------------------------------------------------------------------------------- 1 | /* pcm_out_test.c 2 | ** 3 | ** Copyright 2020, The Android Open Source Project 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above copyright 10 | ** notice, this list of conditions and the following disclaimer in the 11 | ** documentation and/or other materials provided with the distribution. 12 | ** * Neither the name of The Android Open Source Project nor the names of 13 | ** its contributors may be used to endorse or promote products derived 14 | ** from this software without specific prior written permission. 15 | ** 16 | ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND 17 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE 20 | ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 26 | ** DAMAGE. 27 | */ 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include 38 | 39 | #include "tinyalsa/pcm.h" 40 | 41 | #include "pcm_test_device.h" 42 | 43 | namespace tinyalsa { 44 | namespace testing { 45 | 46 | TEST(PcmTest, FormatToBits) { 47 | // FIXME: Should we return 16 bits for INVALID? 48 | ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_INVALID), 16); 49 | 50 | ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S16_LE), 16); 51 | ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S32_LE), 32); 52 | ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S8), 8); 53 | ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S24_LE), 32); 54 | ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S24_3LE), 24); 55 | ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S16_BE), 16); 56 | ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S24_BE), 32); 57 | ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S24_3BE), 24); 58 | ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_S32_BE), 32); 59 | ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_FLOAT_LE), 32); 60 | ASSERT_EQ(pcm_format_to_bits(PCM_FORMAT_FLOAT_BE), 32); 61 | } 62 | 63 | TEST(PcmTest, OpenAndCloseOutPcm) { 64 | pcm *pcm_object = pcm_open(1000, 1000, PCM_OUT, &kDefaultConfig); 65 | ASSERT_FALSE(pcm_is_ready(pcm_object)); 66 | ASSERT_EQ(pcm_close(pcm_object), 0); 67 | 68 | // assume card 0, device 0 is always available 69 | pcm_object = pcm_open(0, 0, PCM_OUT, &kDefaultConfig); 70 | ASSERT_TRUE(pcm_is_ready(pcm_object)); 71 | ASSERT_EQ(pcm_close(pcm_object), 0); 72 | 73 | pcm_object = pcm_open(0, 0, PCM_OUT | PCM_MMAP, &kDefaultConfig); 74 | ASSERT_TRUE(pcm_is_ready(pcm_object)); 75 | ASSERT_EQ(pcm_close(pcm_object), 0); 76 | 77 | pcm_object = pcm_open(0, 0, PCM_OUT | PCM_MMAP | PCM_NOIRQ, &kDefaultConfig); 78 | ASSERT_TRUE(pcm_is_ready(pcm_object)); 79 | ASSERT_EQ(pcm_close(pcm_object), 0); 80 | 81 | pcm_object = pcm_open(0, 0, PCM_OUT | PCM_NONBLOCK, &kDefaultConfig); 82 | ASSERT_TRUE(pcm_is_ready(pcm_object)); 83 | ASSERT_EQ(pcm_close(pcm_object), 0); 84 | 85 | pcm_object = pcm_open(0, 0, PCM_OUT | PCM_MONOTONIC, &kDefaultConfig); 86 | ASSERT_TRUE(pcm_is_ready(pcm_object)); 87 | ASSERT_EQ(pcm_close(pcm_object), 0); 88 | 89 | std::string name = "hw:0,0"; 90 | pcm_object = pcm_open_by_name(name.c_str(), PCM_OUT, &kDefaultConfig); 91 | ASSERT_TRUE(pcm_is_ready(pcm_object)); 92 | ASSERT_EQ(pcm_close(pcm_object), 0); 93 | } 94 | 95 | TEST(PcmTest, OpenWithoutBlocking) { 96 | char loopback_device_info_path[120] = {}; 97 | snprintf(loopback_device_info_path, sizeof(loopback_device_info_path), 98 | "/proc/asound/card%d/pcm%dp/info", kLoopbackCard, kLoopbackPlaybackDevice); 99 | 100 | std::ifstream info_file_stream{loopback_device_info_path}; 101 | if (!info_file_stream.is_open()) { 102 | GTEST_SKIP(); 103 | } 104 | 105 | char buffer[256] = {}; 106 | int32_t subdevice_count = 0; 107 | while (info_file_stream.good()) { 108 | info_file_stream.getline(buffer, sizeof(buffer)); 109 | std::cout << buffer << std::endl; 110 | std::string_view line{buffer}; 111 | if (line.find("subdevices_count") != std::string_view::npos) { 112 | auto subdevice_count_string = line.substr(line.find(":") + 1); 113 | std::cout << subdevice_count_string << std::endl; 114 | subdevice_count = std::stoi(std::string{subdevice_count_string}); 115 | } 116 | } 117 | 118 | ASSERT_GT(subdevice_count, 0); 119 | 120 | auto pcm_array = std::make_unique(subdevice_count); 121 | std::thread *open_thread = new std::thread{[&pcm_array, subdevice_count] { 122 | // Occupy all substreams 123 | for (int32_t i = 0; i < subdevice_count; i++) { 124 | pcm_array[i] = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT, 125 | &kDefaultConfig); 126 | EXPECT_TRUE(pcm_is_ready(pcm_array[i])); 127 | } 128 | 129 | // Expect that pcm_open is not blocked in the kernel and return a bad_object pointer. 130 | pcm *pcm_object = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT, 131 | &kDefaultConfig); 132 | if (pcm_is_ready(pcm_object)) { 133 | // open_thread is blocked in kernel because of the substream is all occupied. pcm_open 134 | // returns because the main thread has released all pcm structures in pcm_array. We just 135 | // need to close the pcm_object here. 136 | pcm_close(pcm_object); 137 | return; 138 | } 139 | 140 | // Release all substreams 141 | for (int32_t i = 0; i < subdevice_count; i++) { 142 | pcm_close(pcm_array[i]); 143 | pcm_array[i] = nullptr; 144 | } 145 | }}; 146 | 147 | static constexpr int64_t kTimeoutMs = 500; 148 | std::this_thread::sleep_for(std::chrono::milliseconds(kTimeoutMs)); 149 | if (pcm_array[0] == nullptr) { 150 | open_thread->join(); 151 | } else { 152 | for (int32_t i = 0; i < subdevice_count; i++) { 153 | pcm_close(pcm_array[i]); 154 | pcm_array[i] = nullptr; 155 | } 156 | open_thread->join(); 157 | FAIL() << "The open_thread is blocked in kernel or the kTimeoutMs(" << kTimeoutMs << 158 | ") is too short to complete"; 159 | } 160 | } 161 | 162 | } // namespace testing 163 | } // namespace tinyalsa 164 | -------------------------------------------------------------------------------- /utils/tinywavinfo.c: -------------------------------------------------------------------------------- 1 | /* tinywavinfo.c 2 | ** 3 | ** Copyright 2015, The Android Open Source Project 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above copyright 10 | ** notice, this list of conditions and the following disclaimer in the 11 | ** documentation and/or other materials provided with the distribution. 12 | ** * Neither the name of The Android Open Source Project nor the names of 13 | ** its contributors may be used to endorse or promote products derived 14 | ** from this software without specific prior written permission. 15 | ** 16 | ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND 17 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE 20 | ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 26 | ** DAMAGE. 27 | */ 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #define ID_RIFF 0x46464952 37 | #define ID_WAVE 0x45564157 38 | #define ID_FMT 0x20746d66 39 | #define ID_DATA 0x61746164 40 | 41 | struct riff_wave_header { 42 | uint32_t riff_id; 43 | uint32_t riff_sz; 44 | uint32_t wave_id; 45 | }; 46 | 47 | struct chunk_header { 48 | uint32_t id; 49 | uint32_t sz; 50 | }; 51 | 52 | struct chunk_fmt { 53 | uint16_t audio_format; 54 | uint16_t num_channels; 55 | uint32_t sample_rate; 56 | uint32_t byte_rate; 57 | uint16_t block_align; 58 | uint16_t bits_per_sample; 59 | }; 60 | 61 | static int close = 0; 62 | 63 | void analyse_sample(FILE *file, unsigned int channels, unsigned int bits, 64 | unsigned int data_chunk_size); 65 | 66 | void stream_close(int sig) 67 | { 68 | /* allow the stream to be closed gracefully */ 69 | signal(sig, SIG_IGN); 70 | close = 1; 71 | } 72 | 73 | size_t xfread(void *ptr, size_t size, size_t nmemb, FILE *stream) 74 | { 75 | size_t sz = fread(ptr, size, nmemb, stream); 76 | 77 | if (sz != nmemb && ferror(stream)) { 78 | fprintf(stderr, "Error: fread failed\n"); 79 | exit(1); 80 | } 81 | return sz; 82 | } 83 | 84 | int main(int argc, char **argv) 85 | { 86 | FILE *file; 87 | struct riff_wave_header riff_wave_header; 88 | struct chunk_header chunk_header; 89 | struct chunk_fmt chunk_fmt; 90 | char *filename; 91 | int more_chunks = 1; 92 | 93 | if (argc < 2) { 94 | fprintf(stderr, "Usage: %s file.wav \n", argv[0]); 95 | return 1; 96 | } 97 | 98 | filename = argv[1]; 99 | file = fopen(filename, "rb"); 100 | if (!file) { 101 | fprintf(stderr, "Unable to open file '%s'\n", filename); 102 | return 1; 103 | } 104 | 105 | xfread(&riff_wave_header, sizeof(riff_wave_header), 1, file); 106 | if ((riff_wave_header.riff_id != ID_RIFF) || 107 | (riff_wave_header.wave_id != ID_WAVE)) { 108 | fprintf(stderr, "Error: '%s' is not a riff/wave file\n", filename); 109 | fclose(file); 110 | return 1; 111 | } 112 | 113 | do { 114 | xfread(&chunk_header, sizeof(chunk_header), 1, file); 115 | 116 | switch (chunk_header.id) { 117 | case ID_FMT: 118 | xfread(&chunk_fmt, sizeof(chunk_fmt), 1, file); 119 | /* If the format header is larger, skip the rest */ 120 | if (chunk_header.sz > sizeof(chunk_fmt)) 121 | fseek(file, chunk_header.sz - sizeof(chunk_fmt), SEEK_CUR); 122 | break; 123 | case ID_DATA: 124 | /* Stop looking for chunks */ 125 | more_chunks = 0; 126 | break; 127 | default: 128 | /* Unknown chunk, skip bytes */ 129 | fseek(file, chunk_header.sz, SEEK_CUR); 130 | } 131 | } while (more_chunks); 132 | 133 | printf("Input File : %s \n", filename); 134 | printf("Channels : %u \n", chunk_fmt.num_channels); 135 | printf("Sample Rate : %u \n", chunk_fmt.sample_rate); 136 | printf("Bits per sample : %u \n\n", chunk_fmt.bits_per_sample); 137 | 138 | analyse_sample(file, chunk_fmt.num_channels, chunk_fmt.bits_per_sample, 139 | chunk_header.sz); 140 | 141 | fclose(file); 142 | 143 | return 0; 144 | } 145 | 146 | void analyse_sample(FILE *file, unsigned int channels, unsigned int bits, 147 | unsigned int data_chunk_size) 148 | { 149 | void *buffer; 150 | int size; 151 | int num_read; 152 | int i; 153 | unsigned int ch; 154 | int frame_size = 1024; 155 | unsigned int bytes_per_sample = 0; 156 | float *power; 157 | int total_sample_per_channel; 158 | float normalization_factor; 159 | 160 | if (bits == 32) 161 | bytes_per_sample = 4; 162 | else if (bits == 16) 163 | bytes_per_sample = 2; 164 | 165 | normalization_factor = (float)pow(2.0, (bits-1)); 166 | 167 | size = channels * bytes_per_sample * frame_size; 168 | 169 | buffer = malloc(size); 170 | if (!buffer) { 171 | fprintf(stderr, "Unable to allocate %d bytes\n", size); 172 | free(buffer); 173 | return; 174 | } 175 | 176 | power = (float *) calloc(channels, sizeof(float)); 177 | 178 | total_sample_per_channel = data_chunk_size / (channels * bytes_per_sample); 179 | 180 | /* catch ctrl-c to shutdown cleanly */ 181 | signal(SIGINT, stream_close); 182 | 183 | do { 184 | num_read = xfread(buffer, 1, size, file); 185 | if (num_read > 0) { 186 | if (2 == bytes_per_sample) { 187 | short *buffer_ptr = (short *)buffer; 188 | for (i = 0; i < num_read / bytes_per_sample; i += channels) { 189 | for (ch = 0; ch < channels; ch++) { 190 | int temp = *buffer_ptr++; 191 | /* Signal Normalization */ 192 | float f = (float) temp / normalization_factor; 193 | *(power + ch) += (float) (f * f); 194 | } 195 | } 196 | } 197 | if (4 == bytes_per_sample) { 198 | int *buffer_ptr = (int *)buffer; 199 | for (i = 0; i < num_read / bytes_per_sample; i += channels) { 200 | for (ch = 0; ch < channels; ch++) { 201 | int temp = *buffer_ptr++; 202 | /* Signal Normalization */ 203 | float f = (float) temp / normalization_factor; 204 | *(power + ch) += (float) (f * f); 205 | } 206 | } 207 | } 208 | } 209 | }while (!close && num_read > 0); 210 | 211 | for (ch = 0; ch < channels; ch++) { 212 | float average_power = 10 * log10((*(power + ch)) / total_sample_per_channel); 213 | if(isinf (average_power)) { 214 | printf("Channel [%2u] Average Power : NO signal or ZERO signal\n", ch); 215 | } else { 216 | printf("Channel [%2u] Average Power : %.2f dB\n", ch, average_power); 217 | } 218 | } 219 | 220 | free(buffer); 221 | free(power); 222 | 223 | } 224 | 225 | -------------------------------------------------------------------------------- /utils/tinypcminfo.c: -------------------------------------------------------------------------------- 1 | /* tinypcminfo.c 2 | ** 3 | ** Copyright 2012, The Android Open Source Project 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above copyright 10 | ** notice, this list of conditions and the following disclaimer in the 11 | ** documentation and/or other materials provided with the distribution. 12 | ** * Neither the name of The Android Open Source Project nor the names of 13 | ** its contributors may be used to endorse or promote products derived 14 | ** from this software without specific prior written permission. 15 | ** 16 | ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND 17 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE 20 | ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 26 | ** DAMAGE. 27 | */ 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #define OPTPARSE_IMPLEMENTATION 35 | #include "optparse.h" 36 | 37 | #ifndef ARRAY_SIZE 38 | #define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) 39 | #endif 40 | 41 | /* The format_lookup is in order of SNDRV_PCM_FORMAT_##index and 42 | * matches the grouping in sound/asound.h. Note this is not 43 | * continuous and has an empty gap from (25 - 30). 44 | */ 45 | static const char *format_lookup[] = { 46 | /*[0] =*/ "S8", 47 | "U8", 48 | "S16_LE", 49 | "S16_BE", 50 | "U16_LE", 51 | "U16_BE", 52 | "S24_LE", 53 | "S24_BE", 54 | "U24_LE", 55 | "U24_BE", 56 | "S32_LE", 57 | "S32_BE", 58 | "U32_LE", 59 | "U32_BE", 60 | "FLOAT_LE", 61 | "FLOAT_BE", 62 | "FLOAT64_LE", 63 | "FLOAT64_BE", 64 | "IEC958_SUBFRAME_LE", 65 | "IEC958_SUBFRAME_BE", 66 | "MU_LAW", 67 | "A_LAW", 68 | "IMA_ADPCM", 69 | "MPEG", 70 | /*[24] =*/ "GSM", 71 | [31] = "SPECIAL", 72 | "S24_3LE", 73 | "S24_3BE", 74 | "U24_3LE", 75 | "U24_3BE", 76 | "S20_3LE", 77 | "S20_3BE", 78 | "U20_3LE", 79 | "U20_3BE", 80 | "S18_3LE", 81 | "S18_3BE", 82 | "U18_3LE", 83 | /*[43] =*/ "U18_3BE", 84 | #if 0 85 | /* recent additions, may not be present on local asound.h */ 86 | "G723_24", 87 | "G723_24_1B", 88 | "G723_40", 89 | "G723_40_1B", 90 | "DSD_U8", 91 | "DSD_U16_LE", 92 | #endif 93 | }; 94 | 95 | /* Returns a human readable name for the format associated with bit_index, 96 | * NULL if bit_index is not known. 97 | */ 98 | static inline const char *pcm_get_format_name(unsigned bit_index) 99 | { 100 | return bit_index < ARRAY_SIZE(format_lookup) ? format_lookup[bit_index] : NULL; 101 | } 102 | 103 | int main(int argc, char **argv) 104 | { 105 | unsigned int device = 0; 106 | unsigned int card = 0; 107 | int i; 108 | struct optparse opts; 109 | struct optparse_long long_options[] = { 110 | { "help", 'h', OPTPARSE_NONE }, 111 | { "card", 'D', OPTPARSE_REQUIRED }, 112 | { "device", 'd', OPTPARSE_REQUIRED }, 113 | { 0, 0, 0 } 114 | }; 115 | 116 | (void)argc; /* silence -Wunused-parameter */ 117 | /* parse command line arguments */ 118 | optparse_init(&opts, argv); 119 | while ((i = optparse_long(&opts, long_options, NULL)) != -1) { 120 | switch (i) { 121 | case 'D': 122 | card = atoi(opts.optarg); 123 | break; 124 | case 'd': 125 | device = atoi(opts.optarg); 126 | break; 127 | case 'h': 128 | fprintf(stderr, "Usage: %s -D card -d device\n", argv[0]); 129 | return 0; 130 | case '?': 131 | fprintf(stderr, "%s\n", opts.errmsg); 132 | return EXIT_FAILURE; 133 | } 134 | } 135 | 136 | printf("Info for card %u, device %u:\n", card, device); 137 | 138 | for (i = 0; i < 2; i++) { 139 | struct pcm_params *params; 140 | const struct pcm_mask *m; 141 | unsigned int min; 142 | unsigned int max; 143 | 144 | printf("\nPCM %s:\n", i == 0 ? "out" : "in"); 145 | 146 | params = pcm_params_get(card, device, i == 0 ? PCM_OUT : PCM_IN); 147 | if (params == NULL) { 148 | printf("Device does not exist.\n"); 149 | continue; 150 | } 151 | 152 | m = pcm_params_get_mask(params, PCM_PARAM_ACCESS); 153 | if (m) { /* bitmask, refer to SNDRV_PCM_ACCESS_*, generally interleaved */ 154 | printf(" Access:\t%#08x\n", m->bits[0]); 155 | } 156 | m = pcm_params_get_mask(params, PCM_PARAM_FORMAT); 157 | if (m) { /* bitmask, refer to: SNDRV_PCM_FORMAT_* */ 158 | unsigned j, k, count = 0; 159 | const unsigned bitcount = sizeof(m->bits[0]) * 8; 160 | 161 | /* we only check first two format masks (out of 8) - others are zero. */ 162 | printf(" Format[0]:\t%#08x\n", m->bits[0]); 163 | printf(" Format[1]:\t%#08x\n", m->bits[1]); 164 | 165 | /* print friendly format names, if they exist */ 166 | for (k = 0; k < 2; ++k) { 167 | for (j = 0; j < bitcount; ++j) { 168 | const char *name; 169 | 170 | if (m->bits[k] & (1 << j)) { 171 | name = pcm_get_format_name(j + k*bitcount); 172 | if (name) { 173 | if (count++ == 0) { 174 | printf(" Format Name:\t"); 175 | } else { 176 | printf (", "); 177 | } 178 | printf("%s", name); 179 | } 180 | } 181 | } 182 | } 183 | if (count) { 184 | printf("\n"); 185 | } 186 | } 187 | m = pcm_params_get_mask(params, PCM_PARAM_SUBFORMAT); 188 | if (m) { /* bitmask, should be 1: SNDRV_PCM_SUBFORMAT_STD */ 189 | printf(" Subformat:\t%#08x\n", m->bits[0]); 190 | } 191 | min = pcm_params_get_min(params, PCM_PARAM_RATE); 192 | max = pcm_params_get_max(params, PCM_PARAM_RATE); 193 | printf(" Rate:\tmin=%uHz\tmax=%uHz\n", min, max); 194 | min = pcm_params_get_min(params, PCM_PARAM_CHANNELS); 195 | max = pcm_params_get_max(params, PCM_PARAM_CHANNELS); 196 | printf(" Channels:\tmin=%u\t\tmax=%u\n", min, max); 197 | min = pcm_params_get_min(params, PCM_PARAM_SAMPLE_BITS); 198 | max = pcm_params_get_max(params, PCM_PARAM_SAMPLE_BITS); 199 | printf(" Sample bits:\tmin=%u\t\tmax=%u\n", min, max); 200 | min = pcm_params_get_min(params, PCM_PARAM_PERIOD_SIZE); 201 | max = pcm_params_get_max(params, PCM_PARAM_PERIOD_SIZE); 202 | printf(" Period size:\tmin=%u\t\tmax=%u\n", min, max); 203 | min = pcm_params_get_min(params, PCM_PARAM_PERIODS); 204 | max = pcm_params_get_max(params, PCM_PARAM_PERIODS); 205 | printf("Period count:\tmin=%u\t\tmax=%u\n", min, max); 206 | 207 | pcm_params_free(params); 208 | } 209 | 210 | return 0; 211 | } 212 | -------------------------------------------------------------------------------- /tests/src/pcm_out_test.cc: -------------------------------------------------------------------------------- 1 | /* pcm_out_test.c 2 | ** 3 | ** Copyright 2020, The Android Open Source Project 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above copyright 10 | ** notice, this list of conditions and the following disclaimer in the 11 | ** documentation and/or other materials provided with the distribution. 12 | ** * Neither the name of The Android Open Source Project nor the names of 13 | ** its contributors may be used to endorse or promote products derived 14 | ** from this software without specific prior written permission. 15 | ** 16 | ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND 17 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE 20 | ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 26 | ** DAMAGE. 27 | */ 28 | #include "pcm_test_device.h" 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | #include "tinyalsa/pcm.h" 37 | 38 | namespace tinyalsa { 39 | namespace testing { 40 | 41 | class PcmOutTest : public ::testing::Test { 42 | protected: 43 | PcmOutTest() : pcm_object(nullptr) {} 44 | virtual ~PcmOutTest() = default; 45 | 46 | virtual void SetUp() override { 47 | pcm_object = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT, &kDefaultConfig); 48 | ASSERT_NE(pcm_object, nullptr); 49 | ASSERT_TRUE(pcm_is_ready(pcm_object)); 50 | } 51 | 52 | virtual void TearDown() override { 53 | ASSERT_EQ(pcm_close(pcm_object), 0); 54 | } 55 | 56 | static constexpr unsigned int kDefaultChannels = 2; 57 | static constexpr unsigned int kDefaultSamplingRate = 48000; 58 | static constexpr unsigned int kDefaultPeriodSize = 1024; 59 | static constexpr unsigned int kDefaultPeriodCount = 3; 60 | static constexpr pcm_config kDefaultConfig = { 61 | .channels = kDefaultChannels, 62 | .rate = kDefaultSamplingRate, 63 | .period_size = kDefaultPeriodSize, 64 | .period_count = kDefaultPeriodCount, 65 | .format = PCM_FORMAT_S16_LE, 66 | .start_threshold = kDefaultPeriodSize, 67 | .stop_threshold = kDefaultPeriodSize * kDefaultPeriodCount, 68 | .silence_threshold = 0, 69 | .silence_size = 0, 70 | }; 71 | 72 | pcm* pcm_object; 73 | }; 74 | 75 | TEST_F(PcmOutTest, GetFileDescriptor) { 76 | ASSERT_GT(pcm_get_file_descriptor(pcm_object), 0); 77 | } 78 | 79 | TEST_F(PcmOutTest, GetChannels) { 80 | ASSERT_EQ(pcm_get_channels(pcm_object), kDefaultConfig.channels); 81 | } 82 | 83 | TEST_F(PcmOutTest, GetSamplingRate) { 84 | ASSERT_EQ(pcm_get_rate(pcm_object), kDefaultConfig.rate); 85 | } 86 | 87 | TEST_F(PcmOutTest, GetFormat) { 88 | ASSERT_EQ(pcm_get_format(pcm_object), kDefaultConfig.format); 89 | 90 | } 91 | 92 | TEST_F(PcmOutTest, GetErrorMessage) { 93 | ASSERT_STREQ(pcm_get_error(pcm_object), ""); 94 | } 95 | 96 | TEST_F(PcmOutTest, GetConfig) { 97 | ASSERT_EQ(pcm_get_config(nullptr), nullptr); 98 | ASSERT_EQ(std::memcmp(pcm_get_config(pcm_object), &kDefaultConfig, sizeof(pcm_config)), 0); 99 | } 100 | 101 | TEST_F(PcmOutTest, SetConfig) { 102 | ASSERT_EQ(pcm_set_config(nullptr, nullptr), -EFAULT); 103 | ASSERT_EQ(pcm_set_config(pcm_object, nullptr), 0); 104 | } 105 | 106 | TEST_F(PcmOutTest, GetBufferSize) { 107 | unsigned int buffer_size = pcm_get_buffer_size(pcm_object); 108 | ASSERT_EQ(buffer_size, kDefaultConfig.period_count * kDefaultConfig.period_size); 109 | } 110 | 111 | TEST_F(PcmOutTest, FramesBytesConvert) { 112 | unsigned int bytes = pcm_frames_to_bytes(pcm_object, 1); 113 | ASSERT_EQ(bytes, pcm_format_to_bits(kDefaultConfig.format) / 8 * kDefaultConfig.channels); 114 | 115 | unsigned int frames = pcm_bytes_to_frames(pcm_object, bytes + 1); 116 | ASSERT_EQ(frames, 1); 117 | } 118 | 119 | TEST_F(PcmOutTest, GetAvailableAndTimestamp) { 120 | unsigned int available = 0; 121 | timespec time = { 0 }; 122 | 123 | ASSERT_LT(pcm_get_htimestamp(nullptr, nullptr, nullptr), 0); 124 | 125 | ASSERT_EQ(pcm_get_htimestamp(pcm_object, &available, &time), 0); 126 | ASSERT_NE(available, 0); 127 | // ASSERT_NE(time.tv_nsec | time.tv_sec, 0); 128 | } 129 | 130 | TEST_F(PcmOutTest, GetSubdevice) { 131 | ASSERT_EQ(pcm_get_subdevice(pcm_object), 0); 132 | } 133 | 134 | TEST_F(PcmOutTest, Readi) { 135 | size_t buffer_size = pcm_frames_to_bytes(pcm_object, kDefaultConfig.period_size); 136 | auto buffer = std::make_unique(buffer_size); 137 | 138 | unsigned int frames = pcm_bytes_to_frames(pcm_object, buffer_size); 139 | ASSERT_EQ(pcm_readi(pcm_object, buffer.get(), frames), -EINVAL); 140 | } 141 | 142 | TEST_F(PcmOutTest, Writei) { 143 | constexpr uint32_t write_count = 20; 144 | 145 | size_t buffer_size = pcm_frames_to_bytes(pcm_object, kDefaultConfig.period_size); 146 | auto buffer = std::make_unique(buffer_size); 147 | for (uint32_t i = 0; i < buffer_size; ++i) { 148 | buffer[i] = static_cast(i); 149 | } 150 | 151 | int written_frames = 0; 152 | unsigned int frames = pcm_bytes_to_frames(pcm_object, buffer_size); 153 | auto start = std::chrono::steady_clock::now(); 154 | for (uint32_t i = 0; i < write_count; ++i) { 155 | written_frames = pcm_writei(pcm_object, buffer.get(), frames); 156 | ASSERT_EQ(written_frames, frames); 157 | } 158 | 159 | std::chrono::duration difference = std::chrono::steady_clock::now() - start; 160 | std::chrono::milliseconds expected_elapsed_time_ms(frames * 161 | (write_count - kDefaultConfig.period_count) / (kDefaultConfig.rate / 1000)); 162 | 163 | std::cout << difference.count() << std::endl; 164 | std::cout << expected_elapsed_time_ms.count() << std::endl; 165 | 166 | ASSERT_NEAR(difference.count() * 1000, expected_elapsed_time_ms.count(), 100); 167 | } 168 | 169 | class PcmOutMmapTest : public PcmOutTest { 170 | protected: 171 | PcmOutMmapTest() = default; 172 | ~PcmOutMmapTest() = default; 173 | 174 | virtual void SetUp() override { 175 | pcm_object = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT | PCM_MMAP, 176 | &kDefaultConfig); 177 | ASSERT_NE(pcm_object, nullptr); 178 | ASSERT_TRUE(pcm_is_ready(pcm_object)); 179 | } 180 | 181 | virtual void TearDown() override { 182 | ASSERT_EQ(pcm_close(pcm_object), 0); 183 | } 184 | }; 185 | 186 | TEST_F(PcmOutMmapTest, Write) { 187 | constexpr uint32_t write_count = 20; 188 | 189 | size_t buffer_size = pcm_frames_to_bytes(pcm_object, kDefaultConfig.period_size); 190 | auto buffer = std::make_unique(buffer_size); 191 | for (uint32_t i = 0; i < buffer_size; ++i) { 192 | buffer[i] = static_cast(i); 193 | } 194 | 195 | int res = 0; 196 | unsigned int frames = pcm_bytes_to_frames(pcm_object, buffer_size); 197 | pcm_start(pcm_object); 198 | auto start = std::chrono::steady_clock::now(); 199 | for (uint32_t i = 0; i < write_count; ++i) { 200 | res = pcm_mmap_write(pcm_object, buffer.get(), buffer_size); 201 | ASSERT_EQ(res, 0); 202 | } 203 | pcm_stop(pcm_object); 204 | 205 | std::chrono::duration difference = std::chrono::steady_clock::now() - start; 206 | std::chrono::milliseconds expected_elapsed_time_ms(frames * 207 | (write_count - kDefaultConfig.period_count) / (kDefaultConfig.rate / 1000)); 208 | 209 | std::cout << difference.count() << std::endl; 210 | std::cout << expected_elapsed_time_ms.count() << std::endl; 211 | 212 | ASSERT_NEAR(difference.count() * 1000, expected_elapsed_time_ms.count(), 100); 213 | } 214 | 215 | } // namespace testing 216 | } // namespace tinyalsa 217 | -------------------------------------------------------------------------------- /examples/sndcardparser/sample_sndcardparser.c: -------------------------------------------------------------------------------- 1 | /* sample_sndcardparser.c 2 | ** 3 | ** Copyright (c) 2021, The Linux Foundation. All rights reserved. 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are 7 | ** met: 8 | ** * Redistributions of source code must retain the above copyright 9 | ** notice, this list of conditions and the following disclaimer. 10 | ** * Redistributions in binary form must reproduce the above 11 | ** copyright notice, this list of conditions and the following 12 | ** disclaimer in the documentation and/or other materials provided 13 | ** with the distribution. 14 | ** * Neither the name of The Linux Foundation nor the names of its 15 | ** contributors may be used to endorse or promote products derived 16 | ** from this software without specific prior written permission. 17 | ** 18 | ** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED 19 | ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 20 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT 21 | ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 22 | ** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 25 | ** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 26 | ** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 27 | ** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 28 | ** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | **/ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) 37 | 38 | #define VIRTUAL_SND_CARD_ID 100 39 | #define MAX_PATH 256 40 | #define BUF_SIZE 1024 41 | 42 | enum snd_node_type { 43 | NODE_TYPE_HW = 0, 44 | NODE_TYPE_PLUGIN, 45 | NODE_TYPE_INVALID, 46 | }; 47 | 48 | enum { 49 | NODE_PCM, 50 | NODE_COMPR, 51 | NODE_MIXER, 52 | NODE_MAX, 53 | }; 54 | 55 | struct snd_node_ops { 56 | /** Function pointer to get card definition */ 57 | void* (*open_card)(unsigned int card); 58 | /** Function pointer to release card definition */ 59 | void (*close_card)(void *card); 60 | /** Get interger type properties from device definition */ 61 | int (*get_int)(void *node, const char *prop, int *val); 62 | /** Get string type properties from device definition */ 63 | int (*get_str)(void *node, const char *prop, char **val); 64 | /** Function pointer to get mixer definition */ 65 | void* (*get_mixer)(void *card); 66 | /** Function pointer to get PCM definition */ 67 | void* (*get_pcm)(void *card, unsigned int id); 68 | /** Function pointer to get COMPRESS definition */ 69 | void* (*get_compress)(void *card, unsigned int id); 70 | }; 71 | 72 | struct snd_dev_def { 73 | unsigned int device; 74 | int type; 75 | const char *name; 76 | const char *so_name; 77 | int playback; //used only for pcm node 78 | int capture; //used only for pcm node 79 | /* add custom props here */ 80 | }; 81 | 82 | struct snd_dev_def_card { 83 | unsigned int card; 84 | char *name; 85 | 86 | /* child device details */ 87 | int num_pcm_nodes; 88 | struct snd_dev_def *pcm_dev_def; 89 | 90 | struct snd_dev_def *mixer_dev_def; 91 | }; 92 | 93 | struct snd_dev_def pcm_devs[] = { 94 | {100, NODE_TYPE_PLUGIN, "PCM100", "libtinyalsav2_example_plugin_pcm.so", 1, 0}, 95 | /* Add other plugin info here */ 96 | }; 97 | 98 | struct snd_dev_def mixer_dev = 99 | {VIRTUAL_SND_CARD_ID, NODE_TYPE_PLUGIN, "virtual-snd-card", "libtinyalsav2_example_plugin_mixer.so", 0, 0}; 100 | 101 | void *snd_card_def_open_card(unsigned int card) 102 | { 103 | struct snd_dev_def_card *card_def = NULL; 104 | struct snd_dev_def *pcm_dev_def = NULL; 105 | struct snd_dev_def *mixer_dev_def = NULL; 106 | int num_pcm = ARRAY_SIZE(pcm_devs); 107 | int i; 108 | 109 | if (card != VIRTUAL_SND_CARD_ID) 110 | return NULL; 111 | 112 | card_def = calloc(1, sizeof(struct snd_dev_def_card)); 113 | if (!card_def) 114 | return card_def; 115 | 116 | card_def->card = card; 117 | card_def->name = strdup("virtual-snd-card"); 118 | 119 | /* fill pcm device node info */ 120 | card_def->num_pcm_nodes = num_pcm; 121 | pcm_dev_def = calloc(num_pcm, sizeof(struct snd_dev_def)); 122 | if (!pcm_dev_def) 123 | goto free_card_def; 124 | 125 | for (i = 0; i < num_pcm; i++) 126 | memcpy(&pcm_dev_def[i], &pcm_devs[i], sizeof(struct snd_dev_def)); 127 | 128 | card_def->pcm_dev_def = pcm_dev_def; 129 | 130 | /* fill mixer device node info */ 131 | mixer_dev_def = calloc(1, sizeof(struct snd_dev_def)); 132 | if (!mixer_dev_def) 133 | goto free_pcm_dev; 134 | 135 | memcpy(mixer_dev_def, &mixer_dev, sizeof(struct snd_dev_def)); 136 | 137 | card_def->mixer_dev_def = mixer_dev_def; 138 | return card_def; 139 | free_pcm_dev: 140 | free(pcm_dev_def); 141 | free_card_def: 142 | free(card_def->name); 143 | free(card_def); 144 | return NULL; 145 | } 146 | 147 | void snd_card_def_close_card(void *card_node) 148 | { 149 | struct snd_dev_def_card *defs = (struct snd_dev_def_card *)card_node; 150 | struct snd_dev_def *pcm_dev_def = NULL; 151 | struct snd_dev_def *mixer_dev_def = NULL; 152 | 153 | if (!defs) 154 | return; 155 | 156 | pcm_dev_def = defs->pcm_dev_def; 157 | if (pcm_dev_def) 158 | free(pcm_dev_def); 159 | 160 | mixer_dev_def = defs->mixer_dev_def; 161 | if (!mixer_dev_def) 162 | goto free_defs; 163 | 164 | free(mixer_dev_def); 165 | free_defs: 166 | free(defs->name); 167 | free(defs); 168 | } 169 | 170 | void *snd_card_def_get_node(void *card_node, unsigned int id, int type) 171 | { 172 | struct snd_dev_def_card *card_def = (struct snd_dev_def_card *)card_node; 173 | struct snd_dev_def *dev_def = NULL; 174 | int i; 175 | 176 | if (!card_def) 177 | return NULL; 178 | 179 | if (type >= NODE_MAX) 180 | return NULL; 181 | 182 | if (type == NODE_MIXER) 183 | return card_def->mixer_dev_def; 184 | 185 | if (type == NODE_PCM) 186 | dev_def = card_def->pcm_dev_def; 187 | 188 | for (i = 0; i < card_def->num_pcm_nodes; i++) { 189 | if (dev_def[i].device == id) { 190 | return &dev_def[i]; 191 | } 192 | } 193 | 194 | return NULL; 195 | } 196 | 197 | int snd_card_def_get_int(void *node, const char *prop, int *val) 198 | { 199 | struct snd_dev_def *dev_def = (struct snd_dev_def *)node; 200 | int ret = -EINVAL; 201 | 202 | if (!dev_def || !prop || !val) 203 | return ret; 204 | 205 | if (!strcmp(prop, "type")) { 206 | *val = dev_def->type; 207 | return 0; 208 | } else if (!strcmp(prop, "id")) { 209 | *val = dev_def->device; 210 | return 0; 211 | } else if (!strcmp(prop, "playback")) { 212 | *val = dev_def->playback; 213 | return 0; 214 | } else if (!strcmp(prop, "capture")) { 215 | *val = dev_def->capture; 216 | return 0; 217 | } 218 | 219 | return ret; 220 | } 221 | 222 | int snd_card_def_get_str(void *node, const char *prop, char **val) 223 | { 224 | struct snd_dev_def *dev_def = (struct snd_dev_def *)node; 225 | int ret = -EINVAL; 226 | 227 | if (!dev_def || !prop) 228 | return ret; 229 | 230 | if (!strcmp(prop, "so-name")) { 231 | if (dev_def->so_name) { 232 | *val = (char *)dev_def->so_name; 233 | return 0; 234 | } 235 | } 236 | 237 | if (!strcmp(prop, "name")) { 238 | if (dev_def->name) { 239 | *val = (char *)dev_def->name; 240 | return 0; 241 | } 242 | } 243 | 244 | return ret; 245 | } 246 | 247 | void *snd_card_def_get_pcm(void *card_node, unsigned int id) 248 | { 249 | return snd_card_def_get_node(card_node, id, NODE_PCM); 250 | } 251 | 252 | void *snd_card_def_get_compress(void *card_node, unsigned int id) 253 | { 254 | return snd_card_def_get_node(card_node, id, NODE_COMPR); 255 | } 256 | 257 | void *snd_card_def_get_mixer(void *card_node) 258 | { 259 | return snd_card_def_get_node(card_node, 1, NODE_MIXER); 260 | } 261 | 262 | struct snd_node_ops snd_card_ops = { 263 | .open_card = snd_card_def_open_card, 264 | .close_card = snd_card_def_close_card, 265 | .get_int = snd_card_def_get_int, 266 | .get_str = snd_card_def_get_str, 267 | .get_pcm = snd_card_def_get_pcm, 268 | .get_compress = snd_card_def_get_compress, 269 | .get_mixer = snd_card_def_get_mixer, 270 | }; 271 | -------------------------------------------------------------------------------- /utils/tinycap.c: -------------------------------------------------------------------------------- 1 | /* tinycap.c 2 | ** 3 | ** Copyright 2011, The Android Open Source Project 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above copyright 10 | ** notice, this list of conditions and the following disclaimer in the 11 | ** documentation and/or other materials provided with the distribution. 12 | ** * Neither the name of The Android Open Source Project nor the names of 13 | ** its contributors may be used to endorse or promote products derived 14 | ** from this software without specific prior written permission. 15 | ** 16 | ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND 17 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE 20 | ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 26 | ** DAMAGE. 27 | */ 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #define OPTPARSE_IMPLEMENTATION 39 | #include "optparse.h" 40 | 41 | #define ID_RIFF 0x46464952 42 | #define ID_WAVE 0x45564157 43 | #define ID_FMT 0x20746d66 44 | #define ID_DATA 0x61746164 45 | 46 | #define FORMAT_PCM 1 47 | 48 | struct wav_header { 49 | uint32_t riff_id; 50 | uint32_t riff_sz; 51 | uint32_t riff_fmt; 52 | uint32_t fmt_id; 53 | uint32_t fmt_sz; 54 | uint16_t audio_format; 55 | uint16_t num_channels; 56 | uint32_t sample_rate; 57 | uint32_t byte_rate; 58 | uint16_t block_align; 59 | uint16_t bits_per_sample; 60 | uint32_t data_id; 61 | uint32_t data_sz; 62 | }; 63 | 64 | int capturing = 1; 65 | int prinfo = 1; 66 | 67 | unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device, 68 | bool use_mmap, unsigned int channels, unsigned int rate, 69 | enum pcm_format format, unsigned int period_size, 70 | unsigned int period_count, unsigned int capture_time); 71 | 72 | void sigint_handler(int sig) 73 | { 74 | if (sig == SIGINT){ 75 | capturing = 0; 76 | } 77 | } 78 | 79 | int main(int argc, char **argv) 80 | { 81 | FILE *file; 82 | struct wav_header header; 83 | unsigned int card = 0; 84 | unsigned int device = 0; 85 | unsigned int channels = 2; 86 | unsigned int rate = 48000; 87 | unsigned int bits = 16; 88 | unsigned int frames; 89 | unsigned int period_size = 1024; 90 | unsigned int period_count = 4; 91 | unsigned int capture_time = UINT_MAX; 92 | bool use_mmap = false; 93 | enum pcm_format format; 94 | int no_header = 0, c; 95 | struct optparse opts; 96 | 97 | if (argc < 2) { 98 | fprintf(stderr, "Usage: %s {file.wav | --} [-D card] [-d device] [-M] [-c channels] " 99 | "[-r rate] [-b bits] [-p period_size] [-n n_periods] [-t time_in_seconds]\n\n" 100 | "Use -- for filename to send raw PCM to stdout\n", argv[0]); 101 | return 1; 102 | } 103 | 104 | if (strcmp(argv[1],"--") == 0) { 105 | file = stdout; 106 | prinfo = 0; 107 | no_header = 1; 108 | } else { 109 | file = fopen(argv[1], "wb"); 110 | if (!file) { 111 | fprintf(stderr, "Unable to create file '%s'\n", argv[1]); 112 | return 1; 113 | } 114 | } 115 | 116 | /* parse command line arguments */ 117 | optparse_init(&opts, argv + 1); 118 | while ((c = optparse(&opts, "D:d:c:r:b:p:n:t:M")) != -1) { 119 | switch (c) { 120 | case 'd': 121 | device = atoi(opts.optarg); 122 | break; 123 | case 'c': 124 | channels = atoi(opts.optarg); 125 | break; 126 | case 'r': 127 | rate = atoi(opts.optarg); 128 | break; 129 | case 'b': 130 | bits = atoi(opts.optarg); 131 | break; 132 | case 'D': 133 | card = atoi(opts.optarg); 134 | break; 135 | case 'p': 136 | period_size = atoi(opts.optarg); 137 | break; 138 | case 'n': 139 | period_count = atoi(opts.optarg); 140 | break; 141 | case 't': 142 | capture_time = atoi(opts.optarg); 143 | break; 144 | case 'M': 145 | use_mmap = true; 146 | break; 147 | case '?': 148 | fprintf(stderr, "%s\n", opts.errmsg); 149 | return EXIT_FAILURE; 150 | } 151 | } 152 | 153 | header.riff_id = ID_RIFF; 154 | header.riff_sz = 0; 155 | header.riff_fmt = ID_WAVE; 156 | header.fmt_id = ID_FMT; 157 | header.fmt_sz = 16; 158 | header.audio_format = FORMAT_PCM; 159 | header.num_channels = channels; 160 | header.sample_rate = rate; 161 | 162 | switch (bits) { 163 | case 32: 164 | format = PCM_FORMAT_S32_LE; 165 | break; 166 | case 24: 167 | format = PCM_FORMAT_S24_LE; 168 | break; 169 | case 16: 170 | format = PCM_FORMAT_S16_LE; 171 | break; 172 | default: 173 | fprintf(stderr, "%u bits is not supported.\n", bits); 174 | fclose(file); 175 | return 1; 176 | } 177 | 178 | header.bits_per_sample = pcm_format_to_bits(format); 179 | header.byte_rate = (header.bits_per_sample / 8) * channels * rate; 180 | header.block_align = channels * (header.bits_per_sample / 8); 181 | header.data_id = ID_DATA; 182 | 183 | /* leave enough room for header */ 184 | if (!no_header) { 185 | fseek(file, sizeof(struct wav_header), SEEK_SET); 186 | } 187 | 188 | /* install signal handler and begin capturing */ 189 | signal(SIGINT, sigint_handler); 190 | frames = capture_sample(file, card, device, use_mmap, 191 | header.num_channels, header.sample_rate, 192 | format, period_size, period_count, capture_time); 193 | if (prinfo) { 194 | printf("Captured %u frames\n", frames); 195 | } 196 | 197 | /* write header now all information is known */ 198 | if (!no_header) { 199 | header.data_sz = frames * header.block_align; 200 | header.riff_sz = header.data_sz + sizeof(header) - 8; 201 | fseek(file, 0, SEEK_SET); 202 | fwrite(&header, sizeof(struct wav_header), 1, file); 203 | } 204 | 205 | fclose(file); 206 | 207 | return 0; 208 | } 209 | 210 | unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device, 211 | bool use_mmap, unsigned int channels, unsigned int rate, 212 | enum pcm_format format, unsigned int period_size, 213 | unsigned int period_count, unsigned int capture_time) 214 | { 215 | struct pcm_config config; 216 | unsigned int pcm_open_flags; 217 | struct pcm *pcm; 218 | char *buffer; 219 | unsigned int size; 220 | unsigned int frames_read; 221 | unsigned int total_frames_read; 222 | unsigned int bytes_per_frame; 223 | 224 | memset(&config, 0, sizeof(config)); 225 | config.channels = channels; 226 | config.rate = rate; 227 | config.period_size = period_size; 228 | config.period_count = period_count; 229 | config.format = format; 230 | config.start_threshold = 0; 231 | config.stop_threshold = 0; 232 | config.silence_threshold = 0; 233 | 234 | pcm_open_flags = PCM_IN; 235 | if (use_mmap) 236 | pcm_open_flags |= PCM_MMAP; 237 | 238 | pcm = pcm_open(card, device, pcm_open_flags, &config); 239 | if (!pcm || !pcm_is_ready(pcm)) { 240 | fprintf(stderr, "Unable to open PCM device (%s)\n", 241 | pcm_get_error(pcm)); 242 | return 0; 243 | } 244 | 245 | size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm)); 246 | buffer = malloc(size); 247 | if (!buffer) { 248 | fprintf(stderr, "Unable to allocate %u bytes\n", size); 249 | pcm_close(pcm); 250 | return 0; 251 | } 252 | 253 | if (prinfo) { 254 | printf("Capturing sample: %u ch, %u hz, %u bit\n", channels, rate, 255 | pcm_format_to_bits(format)); 256 | } 257 | 258 | bytes_per_frame = pcm_frames_to_bytes(pcm, 1); 259 | total_frames_read = 0; 260 | frames_read = 0; 261 | while (capturing) { 262 | frames_read = pcm_readi(pcm, buffer, pcm_get_buffer_size(pcm)); 263 | total_frames_read += frames_read; 264 | if ((total_frames_read / rate) >= capture_time) { 265 | capturing = 0; 266 | } 267 | if (fwrite(buffer, bytes_per_frame, frames_read, file) != frames_read) { 268 | fprintf(stderr,"Error capturing sample\n"); 269 | break; 270 | } 271 | } 272 | 273 | free(buffer); 274 | pcm_close(pcm); 275 | return total_frames_read; 276 | } 277 | 278 | -------------------------------------------------------------------------------- /tests/src/pcm_loopback_test.cc: -------------------------------------------------------------------------------- 1 | /* pcm_loopback_test.c 2 | ** 3 | ** Copyright 2020, The Android Open Source Project 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above copyright 10 | ** notice, this list of conditions and the following disclaimer in the 11 | ** documentation and/or other materials provided with the distribution. 12 | ** * Neither the name of The Android Open Source Project nor the names of 13 | ** its contributors may be used to endorse or promote products derived 14 | ** from this software without specific prior written permission. 15 | ** 16 | ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND 17 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE 20 | ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 26 | ** DAMAGE. 27 | */ 28 | #include "pcm_test_device.h" 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | 38 | #include "tinyalsa/pcm.h" 39 | 40 | namespace tinyalsa { 41 | namespace testing { 42 | 43 | template 44 | class SilenceGenerator { 45 | public: 46 | pcm_format GetFormat() { 47 | return F; 48 | } 49 | 50 | int32_t GetChannels() { 51 | return CH; 52 | }; 53 | 54 | int32_t GetSamplingRate() { 55 | return SR; 56 | }; 57 | 58 | virtual int32_t Read(void *buffer, int32_t size) { 59 | std::memset(buffer, 0, size); 60 | return size; 61 | } 62 | }; 63 | 64 | template 65 | struct PcmFormat { 66 | using Type = void; 67 | static constexpr pcm_format kFormat = F; 68 | static constexpr int32_t kMax = 0; 69 | static constexpr int32_t kMin = 0; 70 | }; 71 | 72 | template<> 73 | struct PcmFormat { 74 | using Type = int16_t; 75 | static constexpr pcm_format kFormat = PCM_FORMAT_S16_LE; 76 | static constexpr Type kMax = std::numeric_limits::max(); 77 | static constexpr Type kMin = std::numeric_limits::min(); 78 | }; 79 | 80 | template<> 81 | struct PcmFormat { 82 | using Type = float; 83 | static constexpr pcm_format kFormat = PCM_FORMAT_FLOAT_LE; 84 | static constexpr Type kMax = 1.0; 85 | static constexpr Type kMin = -1.0; 86 | }; 87 | 88 | // CH: channels 89 | // SR: sampling rate 90 | // FQ: sine wave frequency 91 | // L: max level 92 | template 93 | class SineToneGenerator : public SilenceGenerator { 94 | private: 95 | using Type = typename PcmFormat::Type; 96 | static constexpr double kPi = M_PI; 97 | static constexpr double kStep = FQ * CH * kPi / SR; 98 | 99 | double channels[CH]; 100 | double gain; 101 | 102 | Type GetSample(double radian) { 103 | double sine = std::sin(radian) * gain; 104 | if (sine >= 1.0) { 105 | return PcmFormat::kMax; 106 | } else if (sine <= -1.0) { 107 | return PcmFormat::kMin; 108 | } 109 | return static_cast(sine * PcmFormat::kMax); 110 | } 111 | 112 | public: 113 | SineToneGenerator() { 114 | constexpr double phase = (CH == 1) ? 0 : kPi / 2 / (CH - 1); 115 | 116 | channels[0] = 0.0; 117 | for (int32_t i = 1; i < CH; ++i) { 118 | channels[i] = channels[i - 1] + phase; 119 | } 120 | 121 | gain = std::pow(M_E, std::log(10) * static_cast(L) / 20.0); 122 | } 123 | 124 | ~SineToneGenerator() = default; 125 | 126 | int32_t Read(void *buffer, int32_t size) override { 127 | Type *pcm_buffer = reinterpret_cast(buffer); 128 | 129 | size = (size / (CH * sizeof(Type))) * (CH * sizeof(Type)); 130 | int32_t samples = size / sizeof(Type); 131 | int32_t s = 0; 132 | 133 | while (s < samples) { 134 | for (int32_t i = 0; i < CH; ++i) { 135 | pcm_buffer[s++] = GetSample(channels[i]); 136 | channels[i] += kStep; 137 | } 138 | } 139 | return size; 140 | } 141 | }; 142 | 143 | template 144 | static double Energy(T *buffer, size_t samples) { 145 | double sum = 0.0; 146 | for (size_t i = 0; i < samples; i++) { 147 | sum += static_cast(buffer[i]) * static_cast(buffer[i]); 148 | } 149 | return sum; 150 | } 151 | 152 | template 153 | class PcmLoopbackTest : public ::testing::Test { 154 | protected: 155 | PcmLoopbackTest() = default; 156 | virtual ~PcmLoopbackTest() = default; 157 | 158 | void SetUp() override { 159 | static constexpr pcm_config kInConfig = { 160 | .channels = kDefaultChannels, 161 | .rate = kDefaultSamplingRate, 162 | .period_size = kDefaultPeriodSize, 163 | .period_count = kDefaultPeriodCount, 164 | .format = kPcmForamt, 165 | .start_threshold = 0, 166 | .stop_threshold = 0, 167 | .silence_threshold = 0, 168 | .silence_size = 0, 169 | }; 170 | pcm_in = pcm_open(kLoopbackCard, kLoopbackCaptureDevice, PCM_IN, &kInConfig); 171 | ASSERT_TRUE(pcm_is_ready(pcm_in)); 172 | 173 | static constexpr pcm_config kOutConfig = { 174 | .channels = kDefaultChannels, 175 | .rate = kDefaultSamplingRate, 176 | .period_size = kDefaultPeriodSize, 177 | .period_count = kDefaultPeriodCount, 178 | .format = kPcmForamt, 179 | .start_threshold = kDefaultPeriodSize, 180 | .stop_threshold = kDefaultPeriodSize * kDefaultPeriodCount, 181 | .silence_threshold = 0, 182 | .silence_size = 0, 183 | }; 184 | pcm_out = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT, &kOutConfig); 185 | ASSERT_TRUE(pcm_is_ready(pcm_out)); 186 | ASSERT_EQ(pcm_link(pcm_in, pcm_out), 0); 187 | } 188 | 189 | void TearDown() override { 190 | ASSERT_EQ(pcm_unlink(pcm_in), 0); 191 | pcm_close(pcm_in); 192 | pcm_close(pcm_out); 193 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 194 | } 195 | 196 | static constexpr unsigned int kDefaultPeriodTimeInMs = 197 | kDefaultPeriodSize * 1000 / kDefaultSamplingRate; 198 | static constexpr pcm_format kPcmForamt = F::kFormat; 199 | pcm *pcm_in; 200 | pcm *pcm_out; 201 | }; 202 | 203 | using S16bitlePcmFormat = PcmFormat; 204 | using FloatPcmFormat = PcmFormat; 205 | 206 | using Formats = ::testing::Types; 207 | 208 | TYPED_TEST_SUITE(PcmLoopbackTest, Formats); 209 | 210 | TYPED_TEST(PcmLoopbackTest, Loopback) { 211 | static constexpr unsigned int kDefaultPeriodTimeInMs = this->kDefaultPeriodTimeInMs; 212 | static constexpr pcm_format kPcmForamt = this->kPcmForamt; 213 | pcm *pcm_in = this->pcm_in; 214 | pcm *pcm_out = this->pcm_out; 215 | 216 | bool stopping = false; 217 | ASSERT_EQ(pcm_get_subdevice(pcm_in), pcm_get_subdevice(pcm_out)); 218 | 219 | std::thread capture([pcm_in, &stopping] { 220 | size_t buffer_size = pcm_frames_to_bytes(pcm_in, kDefaultPeriodSize); 221 | unsigned int frames = pcm_bytes_to_frames(pcm_in, buffer_size); 222 | auto buffer = std::make_unique(buffer_size); 223 | int32_t counter = 0; 224 | while (!stopping) { 225 | int res = pcm_readi(pcm_in, buffer.get(), frames); 226 | if (res == -1) { 227 | std::cout << pcm_get_error(pcm_in) << std::endl; 228 | std::this_thread::sleep_for(std::chrono::milliseconds(kDefaultPeriodTimeInMs)); 229 | counter++; 230 | continue; 231 | } 232 | 233 | // Test the energy of the buffer after the sine tone samples fill in the buffer. 234 | // Therefore, check the buffer 5 times later. 235 | if (counter >= 5) { 236 | double e = Energy(buffer.get(), frames * kDefaultChannels); 237 | EXPECT_GT(e, 0.0) << counter; 238 | } 239 | counter++; 240 | } 241 | std::cout << "read count = " << counter << std::endl; 242 | }); 243 | 244 | std::thread playback([pcm_out, &stopping] { 245 | SineToneGenerator generator; 246 | size_t buffer_size = pcm_frames_to_bytes(pcm_out, kDefaultPeriodSize); 247 | unsigned int frames = pcm_bytes_to_frames(pcm_out, buffer_size); 248 | std::cout << buffer_size << std::endl; 249 | auto buffer = std::make_unique(buffer_size); 250 | int32_t counter = 0; 251 | while (!stopping) { 252 | generator.Read(buffer.get(), buffer_size); 253 | EXPECT_EQ(pcm_writei(pcm_out, buffer.get(), frames), frames) << counter; 254 | counter++; 255 | } 256 | std::cout << "write count = " << counter << std::endl; 257 | }); 258 | 259 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 260 | stopping = true; 261 | capture.join(); 262 | playback.join(); 263 | } 264 | 265 | } // namespace testing 266 | } // namespace tinyalsa 267 | -------------------------------------------------------------------------------- /examples/plugins/sample_pcm_plugin.c: -------------------------------------------------------------------------------- 1 | /* sample_mixer_plugin.c 2 | ** 3 | ** Copyright (c) 2021, The Linux Foundation. All rights reserved. 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are 7 | ** met: 8 | ** * Redistributions of source code must retain the above copyright 9 | ** notice, this list of conditions and the following disclaimer. 10 | ** * Redistributions in binary form must reproduce the above 11 | ** copyright notice, this list of conditions and the following 12 | ** disclaimer in the documentation and/or other materials provided 13 | ** with the distribution. 14 | ** * Neither the name of The Linux Foundation nor the names of its 15 | ** contributors may be used to endorse or promote products derived 16 | ** from this software without specific prior written permission. 17 | ** 18 | ** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED 19 | ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 20 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT 21 | ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 22 | ** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 25 | ** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 26 | ** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 27 | ** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 28 | ** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | **/ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | /* 2 words of uint32_t = 64 bits of mask */ 45 | #define PCM_MASK_SIZE (2) 46 | #define PCM_FORMAT_BIT(x) (1ULL << x) 47 | 48 | struct sample_pcm_priv { 49 | FILE *fptr; 50 | int session_id; 51 | int channels; 52 | int bitwidth; 53 | int sample_rate; 54 | unsigned int period_size; 55 | snd_pcm_uframes_t total_size_frames; 56 | }; 57 | 58 | struct pcm_plugin_hw_constraints sample_pcm_constrs = { 59 | .access = 0, 60 | .format = 0, 61 | .bit_width = { 62 | .min = 16, 63 | .max = 32, 64 | }, 65 | .channels = { 66 | .min = 1, 67 | .max = 8, 68 | }, 69 | .rate = { 70 | .min = 8000, 71 | .max = 384000, 72 | }, 73 | .periods = { 74 | .min = 1, 75 | .max = 8, 76 | }, 77 | .period_bytes = { 78 | .min = 96, 79 | .max = 122880, 80 | }, 81 | }; 82 | 83 | static inline struct snd_interval *param_to_interval(struct snd_pcm_hw_params *p, 84 | int n) 85 | { 86 | return &(p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]); 87 | } 88 | 89 | static inline int param_is_interval(int p) 90 | { 91 | return (p >= SNDRV_PCM_HW_PARAM_FIRST_INTERVAL) && 92 | (p <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL); 93 | } 94 | 95 | static unsigned int param_get_int(struct snd_pcm_hw_params *p, int n) 96 | { 97 | if (param_is_interval(n)) { 98 | struct snd_interval *i = param_to_interval(p, n); 99 | if (i->integer) 100 | return i->max; 101 | } 102 | return 0; 103 | } 104 | 105 | static inline struct snd_mask *param_to_mask(struct snd_pcm_hw_params *p, int n) 106 | { 107 | return &(p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK]); 108 | } 109 | 110 | static inline int param_is_mask(int p) 111 | { 112 | return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) && 113 | (p <= SNDRV_PCM_HW_PARAM_LAST_MASK); 114 | } 115 | 116 | static inline int snd_mask_val(const struct snd_mask *mask) 117 | { 118 | int i; 119 | for (i = 0; i < PCM_MASK_SIZE; i++) { 120 | if (mask->bits[i]) 121 | return ffs(mask->bits[i]) + (i << 5) - 1; 122 | } 123 | return 0; 124 | } 125 | 126 | static int alsaformat_to_bitwidth(int format) 127 | { 128 | switch (format) { 129 | case SNDRV_PCM_FORMAT_S32_LE: 130 | case SNDRV_PCM_FORMAT_S24_LE: 131 | return 32; 132 | case SNDRV_PCM_FORMAT_S8: 133 | return 8; 134 | case SNDRV_PCM_FORMAT_S24_3LE: 135 | return 24; 136 | default: 137 | case SNDRV_PCM_FORMAT_S16_LE: 138 | return 16; 139 | }; 140 | } 141 | 142 | static int param_get_mask_val(struct snd_pcm_hw_params *p, 143 | int n) 144 | { 145 | if (param_is_mask(n)) { 146 | struct snd_mask *m = param_to_mask(p, n); 147 | int val = snd_mask_val(m); 148 | 149 | return alsaformat_to_bitwidth(val); 150 | } 151 | return 0; 152 | } 153 | 154 | static int sample_session_open(int sess_id, unsigned int mode, struct sample_pcm_priv *priv) 155 | { 156 | char fname[128]; 157 | 158 | snprintf(fname, 128, "sample_pcm_data_%d.raw", sess_id); 159 | priv->fptr = fopen(fname,"rwb+"); 160 | if (priv->fptr == NULL) { 161 | return -EIO; 162 | } 163 | rewind(priv->fptr); 164 | return 0; 165 | } 166 | 167 | 168 | static int sample_session_write(struct sample_pcm_priv *priv, void *buff, size_t count) 169 | { 170 | uint8_t *data = (uint8_t *)buff; 171 | size_t len; 172 | 173 | len = fwrite(buff, 1, count, priv->fptr); 174 | 175 | if (len != count) 176 | return -EIO; 177 | 178 | return 0; 179 | } 180 | 181 | static int sample_pcm_hw_params(struct pcm_plugin *plugin, 182 | struct snd_pcm_hw_params *params) 183 | { 184 | struct sample_pcm_priv *priv = plugin->priv; 185 | 186 | priv->sample_rate = param_get_int(params, SNDRV_PCM_HW_PARAM_RATE); 187 | priv->channels = param_get_int(params, SNDRV_PCM_HW_PARAM_CHANNELS); 188 | priv->bitwidth = param_get_mask_val(params, SNDRV_PCM_HW_PARAM_FORMAT); 189 | 190 | return 0; 191 | } 192 | 193 | static int sample_pcm_sw_params(struct pcm_plugin *plugin, 194 | struct snd_pcm_sw_params *sparams) 195 | { 196 | return 0; 197 | } 198 | 199 | static int sample_pcm_sync_ptr(struct pcm_plugin *plugin, 200 | struct snd_pcm_sync_ptr *sync_ptr) 201 | { 202 | return 0; 203 | } 204 | 205 | static int sample_pcm_writei_frames(struct pcm_plugin *plugin, struct snd_xferi *x) 206 | { 207 | struct sample_pcm_priv *priv = plugin->priv; 208 | void *buff; 209 | size_t count; 210 | 211 | buff = x->buf; 212 | count = x->frames * (priv->channels * (priv->bitwidth) / 8); 213 | 214 | return sample_session_write(priv, buff, count); 215 | } 216 | 217 | static int sample_pcm_readi_frames(struct pcm_plugin *plugin, struct snd_xferi *x) 218 | { 219 | return 0; 220 | } 221 | 222 | static int sample_pcm_ttstamp(struct pcm_plugin *plugin, int *tstamp) 223 | { 224 | return 0; 225 | } 226 | 227 | static int sample_pcm_prepare(struct pcm_plugin *plugin) 228 | { 229 | return 0; 230 | } 231 | 232 | static int sample_pcm_start(struct pcm_plugin *plugin) 233 | { 234 | return 0; 235 | } 236 | 237 | static int sample_pcm_drop(struct pcm_plugin *plugin) 238 | { 239 | return 0; 240 | } 241 | 242 | static int sample_pcm_close(struct pcm_plugin *plugin) 243 | { 244 | struct sample_pcm_priv *priv = plugin->priv; 245 | int ret = 0; 246 | 247 | fclose(priv->fptr); 248 | free(plugin->priv); 249 | free(plugin); 250 | 251 | return ret; 252 | } 253 | 254 | static int sample_pcm_poll(struct pcm_plugin *plugin, struct pollfd *pfd, 255 | nfds_t nfds, int timeout) 256 | { 257 | return 0; 258 | } 259 | 260 | static void* sample_pcm_mmap(struct pcm_plugin *plugin, void *addr, size_t length, int prot, 261 | int flags, off_t offset) 262 | { 263 | return MAP_FAILED; 264 | } 265 | 266 | static int sample_pcm_munmap(struct pcm_plugin *plugin, void *addr, size_t length) 267 | { 268 | return 0; 269 | } 270 | 271 | int sample_pcm_open(struct pcm_plugin **plugin, unsigned int card, 272 | unsigned int device, unsigned int mode) 273 | { 274 | struct pcm_plugin *sample_pcm_plugin; 275 | struct sample_pcm_priv *priv; 276 | int ret = 0, session_id = device; 277 | 278 | sample_pcm_plugin = calloc(1, sizeof(struct pcm_plugin)); 279 | if (!sample_pcm_plugin) 280 | return -ENOMEM; 281 | 282 | priv = calloc(1, sizeof(struct sample_pcm_priv)); 283 | if (!priv) { 284 | ret = -ENOMEM; 285 | goto err_plugin_free; 286 | } 287 | 288 | sample_pcm_constrs.access = (PCM_FORMAT_BIT(SNDRV_PCM_ACCESS_RW_INTERLEAVED) | 289 | PCM_FORMAT_BIT(SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)); 290 | sample_pcm_constrs.format = (PCM_FORMAT_BIT(SNDRV_PCM_FORMAT_S16_LE) | 291 | PCM_FORMAT_BIT(SNDRV_PCM_FORMAT_S24_LE) | 292 | PCM_FORMAT_BIT(SNDRV_PCM_FORMAT_S24_3LE) | 293 | PCM_FORMAT_BIT(SNDRV_PCM_FORMAT_S32_LE)); 294 | 295 | sample_pcm_plugin->card = card; 296 | sample_pcm_plugin->mode = mode; 297 | sample_pcm_plugin->constraints = &sample_pcm_constrs; 298 | sample_pcm_plugin->priv = priv; 299 | 300 | priv->session_id = session_id; 301 | 302 | ret = sample_session_open(session_id, mode, priv); 303 | if (ret) { 304 | errno = -ret; 305 | goto err_priv_free; 306 | } 307 | *plugin = sample_pcm_plugin; 308 | return 0; 309 | 310 | err_priv_free: 311 | free(priv); 312 | err_plugin_free: 313 | free(sample_pcm_plugin); 314 | return ret; 315 | } 316 | 317 | struct pcm_plugin_ops pcm_plugin_ops = { 318 | .open = sample_pcm_open, 319 | .close = sample_pcm_close, 320 | .hw_params = sample_pcm_hw_params, 321 | .sw_params = sample_pcm_sw_params, 322 | .sync_ptr = sample_pcm_sync_ptr, 323 | .writei_frames = sample_pcm_writei_frames, 324 | .readi_frames = sample_pcm_readi_frames, 325 | .ttstamp = sample_pcm_ttstamp, 326 | .prepare = sample_pcm_prepare, 327 | .start = sample_pcm_start, 328 | .drop = sample_pcm_drop, 329 | .mmap = sample_pcm_mmap, 330 | .munmap = sample_pcm_munmap, 331 | .poll = sample_pcm_poll, 332 | }; 333 | -------------------------------------------------------------------------------- /tests/src/pcm_params_test.cc: -------------------------------------------------------------------------------- 1 | /* pcm_params_test.c 2 | ** 3 | ** Copyright 2020, The Android Open Source Project 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above copyright 10 | ** notice, this list of conditions and the following disclaimer in the 11 | ** documentation and/or other materials provided with the distribution. 12 | ** * Neither the name of The Android Open Source Project nor the names of 13 | ** its contributors may be used to endorse or promote products derived 14 | ** from this software without specific prior written permission. 15 | ** 16 | ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND 17 | ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE 20 | ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 26 | ** DAMAGE. 27 | */ 28 | 29 | #include "pcm_test_device.h" 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | 37 | #include "tinyalsa/pcm.h" 38 | 39 | namespace tinyalsa { 40 | namespace testing { 41 | 42 | static inline unsigned int OrAllBits(const pcm_mask *mask) { 43 | static constexpr size_t kTotalMaskBytes = 32; 44 | unsigned int res = 0; 45 | for (uint32_t i = 0; i < kTotalMaskBytes / sizeof(pcm_mask::bits[0]); ++i) { 46 | res |= mask->bits[i]; 47 | } 48 | return res; 49 | } 50 | 51 | TEST(PcmParamsTest, GetAndFreeParams) { 52 | pcm_params *params = nullptr; 53 | 54 | // test to get nonexistent card and device. 55 | params = pcm_params_get(1000, 1000, PCM_IN); 56 | ASSERT_EQ(params, nullptr); 57 | 58 | // test free null params. 59 | pcm_params_free(params); 60 | 61 | // assume that card 0, device 0 is always available. 62 | params = pcm_params_get(0, 0, PCM_OUT); 63 | ASSERT_NE(params, nullptr); 64 | pcm_params_free(params); 65 | } 66 | 67 | TEST(PcmParamsTest, GetParamsBitMask) { 68 | // test to get mask with null params 69 | ASSERT_EQ(pcm_params_get_mask(nullptr, PCM_PARAM_ACCESS), nullptr); 70 | 71 | // assume that card 0, device 0 is always available. 72 | pcm_params *params = pcm_params_get(0, 0, PCM_OUT); 73 | ASSERT_NE(params, nullptr); 74 | 75 | // test to get param which is not described in bit mask format 76 | ASSERT_EQ(pcm_params_get_mask(params, PCM_PARAM_SAMPLE_BITS), nullptr); 77 | 78 | // test to get mask out of pcm_param enum 79 | ASSERT_EQ(pcm_params_get_mask(params, static_cast(100)), nullptr); 80 | 81 | const pcm_mask *mask = pcm_params_get_mask(params, PCM_PARAM_ACCESS); 82 | ASSERT_NE(mask, nullptr); 83 | 84 | pcm_params_free(params); 85 | } 86 | 87 | TEST(PcmParamsTest, GetParamsInterval) { 88 | // test to get interval with null params 89 | ASSERT_EQ(pcm_params_get_min(nullptr, PCM_PARAM_SAMPLE_BITS), 0); 90 | ASSERT_EQ(pcm_params_get_max(nullptr, PCM_PARAM_SAMPLE_BITS), 0); 91 | 92 | // assume that card 0, device 0 is always available. 93 | pcm_params *params = pcm_params_get(0, 0, PCM_OUT); 94 | ASSERT_NE(params, nullptr); 95 | 96 | // test to get param which is not described in interval format 97 | ASSERT_EQ(pcm_params_get_min(params, PCM_PARAM_ACCESS), 0); 98 | ASSERT_EQ(pcm_params_get_max(params, PCM_PARAM_ACCESS), 0); 99 | 100 | // test to get interval out of pcm_param enum 101 | ASSERT_EQ(pcm_params_get_min(params, static_cast(100)), 0); 102 | ASSERT_EQ(pcm_params_get_max(params, static_cast(100)), 0); 103 | 104 | pcm_params_free(params); 105 | } 106 | 107 | TEST(PcmParamsTest, ParamsToString) { 108 | // assume that card 0, device 0 is always available. 109 | pcm_params *params = pcm_params_get(0, 0, PCM_OUT); 110 | ASSERT_NE(params, nullptr); 111 | 112 | char long_string[1024] = { 0 }; 113 | int count = pcm_params_to_string(params, long_string, sizeof(long_string)); 114 | ASSERT_LE(static_cast(count), sizeof(long_string)); 115 | ASSERT_GT(static_cast(count), 0); 116 | 117 | char short_string[1] = { 0 }; 118 | count = pcm_params_to_string(params, short_string, sizeof(short_string)); 119 | ASSERT_GT(static_cast(count), sizeof(short_string)); 120 | 121 | int proper_string_len = count; 122 | int proper_string_size = proper_string_len + 1; 123 | auto proper_string = std::make_unique(proper_string_size); 124 | count = pcm_params_to_string(params, proper_string.get(), proper_string_size); 125 | ASSERT_GT(static_cast(count), 0); 126 | ASSERT_EQ(static_cast(count), proper_string_len); 127 | ASSERT_EQ(std::strlen(proper_string.get()), proper_string_len); 128 | pcm_params_free(params); 129 | } 130 | 131 | TEST(PcmParamsTest, GetPlaybackDeviceParams) { 132 | pcm_params *params = pcm_params_get(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT); 133 | ASSERT_NE(params, nullptr); 134 | 135 | const pcm_mask *access_mask = pcm_params_get_mask(params, PCM_PARAM_ACCESS); 136 | ASSERT_NE(access_mask, nullptr); 137 | ASSERT_NE(OrAllBits(access_mask), 0); 138 | 139 | const pcm_mask *format_mask = pcm_params_get_mask(params, PCM_PARAM_FORMAT); 140 | ASSERT_NE(format_mask, nullptr); 141 | ASSERT_NE(OrAllBits(format_mask), 0); 142 | 143 | const pcm_mask *subformat_mask = pcm_params_get_mask(params, PCM_PARAM_SUBFORMAT); 144 | ASSERT_NE(subformat_mask, nullptr); 145 | ASSERT_NE(OrAllBits(subformat_mask), 0); 146 | 147 | unsigned int sample_bits_min = pcm_params_get_min(params, PCM_PARAM_SAMPLE_BITS); 148 | unsigned int sample_bits_max = pcm_params_get_max(params, PCM_PARAM_SAMPLE_BITS); 149 | std::cout << "sample_bits: " << sample_bits_min << " - " << sample_bits_max << std::endl; 150 | ASSERT_GT(sample_bits_min, 0); 151 | ASSERT_GT(sample_bits_max, 0); 152 | 153 | unsigned int frame_bits_min = pcm_params_get_min(params, PCM_PARAM_FRAME_BITS); 154 | unsigned int frame_bits_max = pcm_params_get_max(params, PCM_PARAM_FRAME_BITS); 155 | std::cout << "frame_bits: " << frame_bits_min << " - " << frame_bits_max << std::endl; 156 | ASSERT_GT(frame_bits_min, 0); 157 | ASSERT_GT(frame_bits_max, 0); 158 | 159 | unsigned int channels_min = pcm_params_get_min(params, PCM_PARAM_CHANNELS); 160 | unsigned int channels_max = pcm_params_get_max(params, PCM_PARAM_CHANNELS); 161 | std::cout << "channels: " << channels_min << " - " << channels_max << std::endl; 162 | ASSERT_GT(channels_min, 0); 163 | ASSERT_GT(channels_max, 0); 164 | 165 | unsigned int sampling_rate_min = pcm_params_get_min(params, PCM_PARAM_RATE); 166 | unsigned int sampling_rate_max = pcm_params_get_max(params, PCM_PARAM_RATE); 167 | std::cout << "sampling_rate: " << sampling_rate_min << " - " << sampling_rate_max << std::endl; 168 | ASSERT_GT(sampling_rate_min, 0); 169 | ASSERT_GT(sampling_rate_max, 0); 170 | 171 | unsigned int period_time_min = pcm_params_get_min(params, PCM_PARAM_PERIOD_TIME); 172 | unsigned int period_time_max = pcm_params_get_max(params, PCM_PARAM_PERIOD_TIME); 173 | std::cout << "period_time: " << period_time_min << " - " << period_time_max << std::endl; 174 | ASSERT_GT(period_time_min, 0); 175 | ASSERT_GT(period_time_max, 0); 176 | 177 | unsigned int period_size_min = pcm_params_get_min(params, PCM_PARAM_PERIOD_SIZE); 178 | unsigned int period_size_max = pcm_params_get_max(params, PCM_PARAM_PERIOD_SIZE); 179 | std::cout << "period_size: " << period_size_min << " - " << period_size_max << std::endl; 180 | ASSERT_GT(period_size_min, 0); 181 | ASSERT_GT(period_size_max, 0); 182 | 183 | unsigned int period_bytes_min = pcm_params_get_min(params, PCM_PARAM_PERIOD_BYTES); 184 | unsigned int period_bytes_max = pcm_params_get_max(params, PCM_PARAM_PERIOD_BYTES); 185 | std::cout << "period_bytes: " << period_bytes_min << " - " << period_bytes_max << std::endl; 186 | ASSERT_GT(period_bytes_min, 0); 187 | ASSERT_GT(period_bytes_max, 0); 188 | 189 | unsigned int period_count_min = pcm_params_get_min(params, PCM_PARAM_PERIODS); 190 | unsigned int period_count_max = pcm_params_get_max(params, PCM_PARAM_PERIODS); 191 | std::cout << "period_count: " << period_count_min << " - " << period_count_max << std::endl; 192 | ASSERT_GT(period_count_min, 0); 193 | ASSERT_GT(period_count_max, 0); 194 | 195 | unsigned int buffer_time_min = pcm_params_get_min(params, PCM_PARAM_BUFFER_TIME); 196 | unsigned int buffer_time_max = pcm_params_get_max(params, PCM_PARAM_BUFFER_TIME); 197 | std::cout << "buffer_time: " << buffer_time_min << " - " << buffer_time_max << std::endl; 198 | ASSERT_GT(buffer_time_min, 0); 199 | ASSERT_GT(buffer_time_max, 0); 200 | 201 | unsigned int buffer_size_min = pcm_params_get_min(params, PCM_PARAM_BUFFER_SIZE); 202 | unsigned int buffer_size_max = pcm_params_get_max(params, PCM_PARAM_BUFFER_SIZE); 203 | std::cout << "buffer_size: " << buffer_size_min << " - " << buffer_size_max << std::endl; 204 | ASSERT_GT(buffer_size_min, 0); 205 | ASSERT_GT(buffer_size_max, 0); 206 | 207 | unsigned int buffer_bytes_min = pcm_params_get_min(params, PCM_PARAM_BUFFER_BYTES); 208 | unsigned int buffer_bytes_max = pcm_params_get_max(params, PCM_PARAM_BUFFER_BYTES); 209 | std::cout << "buffer_bytes: " << buffer_bytes_min << " - " << buffer_bytes_max << std::endl; 210 | ASSERT_GT(buffer_bytes_min, 0); 211 | ASSERT_GT(buffer_bytes_max, 0); 212 | 213 | unsigned int tick_in_us_min = pcm_params_get_min(params, PCM_PARAM_TICK_TIME); 214 | unsigned int tick_in_us_max = pcm_params_get_max(params, PCM_PARAM_TICK_TIME); 215 | ASSERT_GT(tick_in_us_max, 0); 216 | std::cout << "tick_in_us: " << tick_in_us_min << " - " << tick_in_us_max << std::endl; 217 | 218 | pcm_params_free(params); 219 | } 220 | 221 | } // namespace testing 222 | } // namespace tinyalsa 223 | -------------------------------------------------------------------------------- /examples/plugins/sample_mixer_plugin.c: -------------------------------------------------------------------------------- 1 | /* sample_mixer_plugin.c 2 | ** 3 | ** Copyright (c) 2021, The Linux Foundation. All rights reserved. 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are 7 | ** met: 8 | ** * Redistributions of source code must retain the above copyright 9 | ** notice, this list of conditions and the following disclaimer. 10 | ** * Redistributions in binary form must reproduce the above 11 | ** copyright notice, this list of conditions and the following 12 | ** disclaimer in the documentation and/or other materials provided 13 | ** with the distribution. 14 | ** * Neither the name of The Linux Foundation nor the names of its 15 | ** contributors may be used to endorse or promote products derived 16 | ** from this software without specific prior written permission. 17 | ** 18 | ** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED 19 | ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 20 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT 21 | ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 22 | ** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 25 | ** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 26 | ** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 27 | ** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 28 | ** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | **/ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | 41 | #define SAMPLE_MIXER_PRIV_GET_CTL_PTR(p, idx) (p->ctls + idx) 42 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) 43 | 44 | static const char *const sample_enum_text[] = {"One", "Two", "Three"}; 45 | 46 | struct sample_mixer_priv { 47 | struct snd_control *ctls; 48 | int ctl_count; 49 | 50 | struct snd_value_enum sample_enum; 51 | 52 | mixer_event_callback event_cb; 53 | }; 54 | 55 | static int sample_mixer_int_ctl_get(struct mixer_plugin *plugin, 56 | struct snd_control *ctl, struct snd_ctl_elem_value *ev) 57 | { 58 | return 0; 59 | } 60 | 61 | static int sample_mixer_int_ctl_put(struct mixer_plugin *plugin, 62 | struct snd_control *ctl, struct snd_ctl_elem_value *ev) 63 | { 64 | /* 65 | * Integer values can be retrieved using: 66 | * uint32_t val1 = (uint32_t)ev->value.integer.value[0]; 67 | * uint32_t val2 = (uint32_t)ev->value.integer.value[1]; 68 | * uint32_t val3 = (uint32_t)ev->value.integer.value[2]; 69 | */ 70 | return 0; 71 | } 72 | 73 | static int sample_mixer_byte_array_ctl_get(struct mixer_plugin *plugin, 74 | struct snd_control *ctl, struct snd_ctl_elem_value *ev) 75 | { 76 | return 0; 77 | } 78 | 79 | static int sample_mixer_byte_array_ctl_put(struct mixer_plugin *plugin, 80 | struct snd_control *ctl, struct snd_ctl_elem_value *ev) 81 | { 82 | /* 83 | * Byte array payload can be retrieved using: 84 | * void *payload = ev->value.bytes.data; 85 | */ 86 | 87 | return 0; 88 | } 89 | 90 | static int sample_mixer_tlv_ctl_get(struct mixer_plugin *plugin, 91 | struct snd_control *ctl, struct snd_ctl_tlv *ev) 92 | { 93 | return 0; 94 | } 95 | 96 | static int sample_mixer_tlv_ctl_put(struct mixer_plugin *plugin, 97 | struct snd_control *ctl, struct snd_ctl_tlv *tlv) 98 | { 99 | /* 100 | * TLV payload and len can be retrieved using: 101 | * void *payload = &tlv->tlv[0]; 102 | * size_t tlv_size = tlv->length; 103 | */ 104 | 105 | return 0; 106 | } 107 | 108 | static int sample_mixer_enum_ctl_get(struct mixer_plugin *plugin, 109 | struct snd_control *ctl, struct snd_ctl_elem_value *ev) 110 | { 111 | return 0; 112 | } 113 | 114 | static int sample_mixer_enum_ctl_put(struct mixer_plugin *plugin, 115 | struct snd_control *ctl, struct snd_ctl_elem_value *ev) 116 | { 117 | /* 118 | * Enum value can be retrieved using: 119 | * unsigned int val = ev->value.enumerated.item[0]; 120 | */ 121 | return 0; 122 | } 123 | 124 | static struct snd_value_int sample_mixer_ctl_value_int = 125 | SND_VALUE_INTEGER(3, 0, 1000, 100); 126 | 127 | /* 512 max bytes for non-tlv byte controls */ 128 | static struct snd_value_bytes byte_array_ctl_bytes = 129 | SND_VALUE_BYTES(512); 130 | 131 | static struct snd_value_tlv_bytes sample_mixer_tlv_ctl_bytes = 132 | SND_VALUE_TLV_BYTES(1024, sample_mixer_tlv_ctl_get, sample_mixer_tlv_ctl_put); 133 | 134 | static void create_integer_ctl(struct sample_mixer_priv *priv, 135 | int ctl_idx, int pval, void *pdata) 136 | { 137 | struct snd_control *ctl = SAMPLE_MIXER_PRIV_GET_CTL_PTR(priv, ctl_idx); 138 | char *ctl_name = strdup("Sample integer control"); 139 | 140 | /* pval and pdata can be retrieved using snd_control during get()/put() */ 141 | INIT_SND_CONTROL_INTEGER(ctl, ctl_name, sample_mixer_int_ctl_get, 142 | sample_mixer_int_ctl_put, sample_mixer_ctl_value_int, pval, pdata); 143 | } 144 | 145 | static void create_byte_array_ctl(struct sample_mixer_priv *priv, 146 | int ctl_idx, int pval, void *pdata) 147 | { 148 | struct snd_control *ctl = SAMPLE_MIXER_PRIV_GET_CTL_PTR(priv, ctl_idx); 149 | char *ctl_name = strdup("Sample byte array control"); 150 | 151 | INIT_SND_CONTROL_BYTES(ctl, ctl_name, sample_mixer_byte_array_ctl_get, 152 | sample_mixer_byte_array_ctl_put, byte_array_ctl_bytes, 153 | pval, pdata); 154 | } 155 | 156 | static void create_tlv_ctl(struct sample_mixer_priv *priv, 157 | int ctl_idx, int pval, void *pdata) 158 | { 159 | struct snd_control *ctl = SAMPLE_MIXER_PRIV_GET_CTL_PTR(priv, ctl_idx); 160 | char *ctl_name = strdup("Sample tlv control"); 161 | 162 | INIT_SND_CONTROL_TLV_BYTES(ctl, ctl_name, sample_mixer_tlv_ctl_bytes, 163 | pval, pdata); 164 | } 165 | 166 | static void create_enum_ctl(struct sample_mixer_priv *priv, 167 | int ctl_idx, struct snd_value_enum *e, 168 | int pval, void *pdata) 169 | { 170 | struct snd_control *ctl = SAMPLE_MIXER_PRIV_GET_CTL_PTR(priv, ctl_idx); 171 | char *ctl_name = strdup("Sample enum control"); 172 | 173 | INIT_SND_CONTROL_ENUM(ctl, ctl_name, sample_mixer_enum_ctl_get, 174 | sample_mixer_enum_ctl_put, e, pval, pdata); 175 | } 176 | 177 | static int sample_mixer_form_ctls(struct sample_mixer_priv *priv, int ctl_idx) 178 | { 179 | create_integer_ctl(priv, ctl_idx, 0, NULL); 180 | ctl_idx++; 181 | create_byte_array_ctl(priv, ctl_idx, 0, NULL); 182 | ctl_idx++; 183 | create_tlv_ctl(priv, ctl_idx, 0, NULL); 184 | ctl_idx++; 185 | create_enum_ctl(priv, ctl_idx, &priv->sample_enum, 0, NULL); 186 | ctl_idx++; 187 | 188 | return 0; 189 | } 190 | 191 | static ssize_t sample_mixer_read_event(struct mixer_plugin *plugin, 192 | struct snd_ctl_event *ev, size_t size) 193 | { 194 | /* Fill snd_ctl_event *ev before sending. 195 | * Return : sizeof(struct snd_ctl_event), 196 | * 0 in case no event present. 197 | */ 198 | 199 | return 0; 200 | } 201 | 202 | static int sample_mixer_subscribe_events(struct mixer_plugin *plugin, 203 | mixer_event_callback event_cb) 204 | { 205 | struct sample_mixer_priv *priv = plugin->priv; 206 | 207 | priv->event_cb = event_cb; 208 | /* event_cb is the callback function which needs to be called 209 | * when an event occurs. This will unblock poll() on mixer fd 210 | * which is called from mixer_wait_event(). 211 | * Once poll is unblocked, clients can call mixer_read_event() 212 | * During unsubscribe(), event_cb is NULL. 213 | */ 214 | return 0; 215 | } 216 | 217 | static int sample_mixer_alloc_ctls(struct sample_mixer_priv *priv) 218 | { 219 | int ret = 0, i; 220 | 221 | priv->ctls = calloc(priv->ctl_count, sizeof(*priv->ctls)); 222 | if (!priv->ctls) { 223 | return -ENOMEM; 224 | } 225 | 226 | priv->sample_enum.items = ARRAY_SIZE(sample_enum_text); 227 | priv->sample_enum.texts = calloc(priv->sample_enum.items, sizeof(*priv->sample_enum.texts)); 228 | 229 | for (i = 0; i < ARRAY_SIZE(sample_enum_text); i++) 230 | priv->sample_enum.texts[i] = strdup(sample_enum_text[i]); 231 | 232 | return sample_mixer_form_ctls(priv, 0); 233 | } 234 | 235 | static void sample_mixer_free_ctls(struct sample_mixer_priv *priv) 236 | { 237 | int num_enums, i; 238 | struct snd_control *ctl = NULL; 239 | 240 | for (i = 0; i < priv->ctl_count; i++) { 241 | ctl = SAMPLE_MIXER_PRIV_GET_CTL_PTR(priv, i); 242 | if (ctl->name) 243 | free((void *)ctl->name); 244 | } 245 | 246 | num_enums = priv->sample_enum.items; 247 | 248 | for (i = 0; i < num_enums; i++) 249 | free(priv->sample_enum.texts[i]); 250 | 251 | free(priv->sample_enum.texts); 252 | priv->ctl_count = 0; 253 | 254 | if (priv->ctls) { 255 | free(priv->ctls); 256 | priv->ctls = NULL; 257 | } 258 | } 259 | 260 | static void sample_mixer_close(struct mixer_plugin **plugin) 261 | { 262 | struct mixer_plugin *mp = *plugin; 263 | struct sample_mixer_priv *priv = mp->priv; 264 | 265 | /* unblock mixer event during close */ 266 | if (priv->event_cb) 267 | priv->event_cb(mp); 268 | sample_mixer_subscribe_events(mp, NULL); 269 | sample_mixer_free_ctls(priv); 270 | free(priv); 271 | free(*plugin); 272 | *plugin = NULL; 273 | } 274 | 275 | int sample_mixer_open(struct mixer_plugin **plugin, unsigned int card) 276 | { 277 | struct mixer_plugin *mp; 278 | struct sample_mixer_priv *priv; 279 | int i, ret = 0; 280 | int ctl_cnt = 4; 281 | 282 | mp = calloc(1, sizeof(*mp)); 283 | if (!mp) { 284 | return -ENOMEM; 285 | } 286 | 287 | priv = calloc(1, sizeof(*priv)); 288 | if (!priv) { 289 | ret = -ENOMEM; 290 | goto err_priv_alloc; 291 | } 292 | 293 | priv->ctl_count = ctl_cnt; 294 | ret = sample_mixer_alloc_ctls(priv); 295 | if (ret) 296 | goto err_ctls_alloc; 297 | 298 | /* Register the controls */ 299 | mp->controls = priv->ctls; 300 | mp->num_controls = priv->ctl_count; 301 | mp->priv = priv; 302 | *plugin = mp; 303 | 304 | return 0; 305 | 306 | err_ctls_alloc: 307 | sample_mixer_free_ctls(priv); 308 | free(priv); 309 | 310 | err_priv_alloc: 311 | free(mp); 312 | return ret; 313 | } 314 | 315 | struct mixer_plugin_ops mixer_plugin_ops = { 316 | .open = sample_mixer_open, 317 | .close = sample_mixer_close, 318 | .subscribe_events = sample_mixer_subscribe_events, 319 | .read_event = sample_mixer_read_event, 320 | }; 321 | -------------------------------------------------------------------------------- /include/tinyalsa/plugin.h: -------------------------------------------------------------------------------- 1 | /* plugin.h 2 | ** Copyright (c) 2019-2020, The Linux Foundation. 3 | ** 4 | ** Redistribution and use in source and binary forms, with or without 5 | ** modification, are permitted provided that the following conditions are 6 | ** met: 7 | ** * Redistributions of source code must retain the above copyright 8 | ** notice, this list of conditions and the following disclaimer. 9 | ** * Redistributions in binary form must reproduce the above 10 | ** copyright notice, this list of conditions and the following 11 | ** disclaimer in the documentation and/or other materials provided 12 | ** with the distribution. 13 | ** * Neither the name of The Linux Foundation nor the names of its 14 | ** contributors may be used to endorse or promote products derived 15 | ** from this software without specific prior written permission. 16 | ** 17 | ** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED 18 | ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT 20 | ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 21 | ** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 24 | ** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 25 | ** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 26 | ** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 27 | ** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | **/ 29 | 30 | #ifndef TINYALSA_PLUGIN_H 31 | #define TINYALSA_PLUGIN_H 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include 40 | 41 | /* static initializers */ 42 | 43 | #define SND_VALUE_ENUM(etexts, eitems) \ 44 | {.texts = etexts, .items = eitems} 45 | 46 | #define SND_VALUE_BYTES(csize) \ 47 | {.size = csize } 48 | 49 | #define SND_VALUE_INTEGER(icount, imin, imax, istep) \ 50 | {.count = icount, .min = imin, .max = imax, .step = istep } 51 | 52 | #define SND_VALUE_TLV_BYTES(csize, cget, cput) \ 53 | {.size = csize, .get = cget, .put = cput } 54 | 55 | /* pointer based initializers */ 56 | #define INIT_SND_CONTROL_INTEGER(c, cname, cget, cput, cint, pval, pdata) \ 57 | { \ 58 | c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \ 59 | c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; \ 60 | c->type = SNDRV_CTL_ELEM_TYPE_INTEGER; \ 61 | c->name = cname; c->value = &cint; c->get = cget; c->put = cput; \ 62 | c->private_value = pval; c->private_data = pdata; \ 63 | } 64 | 65 | #define INIT_SND_CONTROL_BYTES(c, cname, cget, cput, cint, pval, pdata) \ 66 | { \ 67 | c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \ 68 | c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; \ 69 | c->type = SNDRV_CTL_ELEM_TYPE_BYTES; \ 70 | c->name = cname; c->value = &cint; c->get = cget; c->put = cput; \ 71 | c->private_value = pval; c->private_data = pdata; \ 72 | } 73 | 74 | #define INIT_SND_CONTROL_ENUM(c, cname, cget, cput, cenum, pval, pdata) \ 75 | { \ 76 | c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \ 77 | c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; \ 78 | c->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; \ 79 | c->name = cname; c->value = cenum; c->get = cget; c->put = cput; \ 80 | c->private_value = pval; c->private_data = pdata; \ 81 | } 82 | 83 | #define INIT_SND_CONTROL_TLV_BYTES(c, cname, cbytes, priv_val, priv_data) \ 84 | { \ 85 | c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \ 86 | c->access = SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE; \ 87 | c->type = SNDRV_CTL_ELEM_TYPE_BYTES; \ 88 | c->name = cname; c->value = &cbytes; \ 89 | c->private_value = priv_val; c->private_data = priv_data; \ 90 | } 91 | 92 | struct mixer_plugin; 93 | struct pcm_plugin; 94 | struct snd_node; 95 | 96 | /** Operations that are required to be registered by the plugin. 97 | * @ingroup libtinyalsa-pcm 98 | */ 99 | struct pcm_plugin_ops { 100 | /** open the pcm plugin */ 101 | int (*open) (struct pcm_plugin **plugin, unsigned int card, 102 | unsigned int device, unsigned int flags); 103 | /** close the pcm plugin */ 104 | int (*close) (struct pcm_plugin *plugin); 105 | /** Set the PCM hardware parameters to the plugin */ 106 | int (*hw_params) (struct pcm_plugin *plugin, 107 | struct snd_pcm_hw_params *params); 108 | /** Set the PCM software parameters to the plugin */ 109 | int (*sw_params) (struct pcm_plugin *plugin, 110 | struct snd_pcm_sw_params *params); 111 | /** Synchronize the pointer */ 112 | int (*sync_ptr) (struct pcm_plugin *plugin, 113 | struct snd_pcm_sync_ptr *sync_ptr); 114 | /** Write frames to plugin to be rendered to output */ 115 | int (*writei_frames) (struct pcm_plugin *plugin, 116 | struct snd_xferi *x); 117 | /** Read frames from plugin captured from input */ 118 | int (*readi_frames) (struct pcm_plugin *plugin, 119 | struct snd_xferi *x); 120 | /** Obtain the timestamp for the PCM */ 121 | int (*ttstamp) (struct pcm_plugin *plugin, 122 | int *tstamp); 123 | /** Prepare the plugin for data transfer */ 124 | int (*prepare) (struct pcm_plugin *plugin); 125 | /** Start data transfer from/to the plugin */ 126 | int (*start) (struct pcm_plugin *plugin); 127 | /** Signal the plugin to drain PCM */ 128 | int (*drain) (struct pcm_plugin *plugin); 129 | /** Stop a PCM dropping pending frames if drain() is NOT called. 130 | * Stop a PCM preserving pending frames if drain() is called. */ 131 | int (*drop) (struct pcm_plugin *plugin); 132 | /** Any custom or alsa specific ioctl implementation */ 133 | int (*ioctl) (struct pcm_plugin *plugin, 134 | int cmd, void *arg); 135 | void *(*mmap) (struct pcm_plugin *plugin, void *addr, size_t length, 136 | int prot, int flags, off_t offset); 137 | int (*munmap) (struct pcm_plugin *plugin, void *addr, size_t length); 138 | int (*poll) (struct pcm_plugin *plugin, struct pollfd *pfd, nfds_t nfds, 139 | int timeout); 140 | }; 141 | 142 | /** Minimum and maximum values for hardware parameter constraints. 143 | * @ingroup libtinyalsa-pcm 144 | */ 145 | struct pcm_plugin_min_max { 146 | /** Minimum value for the hardware parameter */ 147 | unsigned int min; 148 | /** Maximum value for the hardware parameter */ 149 | unsigned int max; 150 | }; 151 | 152 | /** Encapsulate the hardware parameter constraints 153 | * @ingroup libtinyalsa-pcm 154 | */ 155 | struct pcm_plugin_hw_constraints { 156 | /** Value for SNDRV_PCM_HW_PARAM_ACCESS param */ 157 | uint64_t access; 158 | /** Value for SNDRV_PCM_HW_PARAM_FORMAT param. 159 | * As of this implementation ALSA supports 52 formats */ 160 | uint64_t format; 161 | /** Value for SNDRV_PCM_HW_PARAM_SAMPLE_BITS param */ 162 | struct pcm_plugin_min_max bit_width; 163 | /** Value for SNDRV_PCM_HW_PARAM_CHANNELS param */ 164 | struct pcm_plugin_min_max channels; 165 | /** Value for SNDRV_PCM_HW_PARAM_RATE param */ 166 | struct pcm_plugin_min_max rate; 167 | /** Value for SNDRV_PCM_HW_PARAM_PERIODS param */ 168 | struct pcm_plugin_min_max periods; 169 | /** Value for SNDRV_PCM_HW_PARAM_PERIOD_BYTES param */ 170 | struct pcm_plugin_min_max period_bytes; 171 | }; 172 | 173 | struct pcm_plugin { 174 | /** Card number for the pcm device */ 175 | unsigned int card; 176 | /** device number for the pcm device */ 177 | unsigned int device; 178 | /** pointer to the contraints registered by the plugin */ 179 | struct pcm_plugin_hw_constraints *constraints; 180 | /** Indicates read/write mode, etc.. */ 181 | int mode; 182 | /* Pointer to hold the plugin's private data */ 183 | void *priv; 184 | /* Tracks the plugin state */ 185 | unsigned int state; 186 | }; 187 | 188 | typedef void (*mixer_event_callback)(struct mixer_plugin *); 189 | 190 | struct mixer_plugin_ops { 191 | int (*open) (struct mixer_plugin **plugin, unsigned int card); 192 | void (*close) (struct mixer_plugin **plugin); 193 | int (*subscribe_events) (struct mixer_plugin *plugin, 194 | mixer_event_callback event_cb); 195 | ssize_t (*read_event) (struct mixer_plugin *plugin, 196 | struct snd_ctl_event *ev, size_t size); 197 | }; 198 | 199 | struct snd_control { 200 | snd_ctl_elem_iface_t iface; 201 | unsigned int access; 202 | const char *name; 203 | snd_ctl_elem_type_t type; 204 | void *value; 205 | int (*get) (struct mixer_plugin *plugin, 206 | struct snd_control *control, 207 | struct snd_ctl_elem_value *ev); 208 | int (*put) (struct mixer_plugin *plugin, 209 | struct snd_control *control, 210 | struct snd_ctl_elem_value *ev); 211 | uint32_t private_value; 212 | void *private_data; 213 | }; 214 | 215 | struct mixer_plugin { 216 | unsigned int card; 217 | void *priv; 218 | 219 | int eventfd; 220 | int subscribed; 221 | int event_cnt; 222 | 223 | struct snd_control *controls; 224 | unsigned int num_controls; 225 | }; 226 | 227 | struct snd_value_enum { 228 | unsigned int items; 229 | char **texts; 230 | }; 231 | 232 | struct snd_value_bytes { 233 | unsigned int size; 234 | }; 235 | 236 | struct snd_value_tlv_bytes { 237 | unsigned int size; 238 | int (*get) (struct mixer_plugin *plugin, 239 | struct snd_control *control, 240 | struct snd_ctl_tlv *tlv); 241 | int (*put) (struct mixer_plugin *plugin, 242 | struct snd_control *control, 243 | struct snd_ctl_tlv *tlv); 244 | }; 245 | 246 | struct snd_value_int { 247 | unsigned int count; 248 | int min; 249 | int max; 250 | int step; 251 | }; 252 | 253 | /** Operations defined by the plugin. 254 | * */ 255 | struct snd_node_ops { 256 | /** Function pointer to get card definition */ 257 | void* (*open_card)(unsigned int card); 258 | /** Function pointer to release card definition */ 259 | void (*close_card)(void *card); 260 | /** Get interger type properties from device definition */ 261 | int (*get_int)(void *node, const char *prop, int *val); 262 | /** Get string type properties from device definition */ 263 | int (*get_str)(void *node, const char *prop, char **val); 264 | /** Function pointer to get mixer definition */ 265 | void* (*get_mixer)(void *card); 266 | /** Function pointer to get PCM definition */ 267 | void* (*get_pcm)(void *card, unsigned int id); 268 | /** Reserved for other nodes such as compress */ 269 | void* reserved[4]; 270 | }; 271 | 272 | #endif /* end of TINYALSA_PLUGIN_H */ 273 | -------------------------------------------------------------------------------- /utils/optparse.h: -------------------------------------------------------------------------------- 1 | /* Optparse --- portable, reentrant, embeddable, getopt-like option parser 2 | * 3 | * This is free and unencumbered software released into the public domain. 4 | * 5 | * To get the implementation, define OPTPARSE_IMPLEMENTATION. 6 | * Optionally define OPTPARSE_API to control the API's visibility 7 | * and/or linkage (static, __attribute__, __declspec). 8 | * 9 | * The POSIX getopt() option parser has three fatal flaws. These flaws 10 | * are solved by Optparse. 11 | * 12 | * 1) Parser state is stored entirely in global variables, some of 13 | * which are static and inaccessible. This means only one thread can 14 | * use getopt(). It also means it's not possible to recursively parse 15 | * nested sub-arguments while in the middle of argument parsing. 16 | * Optparse fixes this by storing all state on a local struct. 17 | * 18 | * 2) The POSIX standard provides no way to properly reset the parser. 19 | * This means for portable code that getopt() is only good for one 20 | * run, over one argv with one option string. It also means subcommand 21 | * options cannot be processed with getopt(). Most implementations 22 | * provide a method to reset the parser, but it's not portable. 23 | * Optparse provides an optparse_arg() function for stepping over 24 | * subcommands and continuing parsing of options with another option 25 | * string. The Optparse struct itself can be passed around to 26 | * subcommand handlers for additional subcommand option parsing. A 27 | * full reset can be achieved by with an additional optparse_init(). 28 | * 29 | * 3) Error messages are printed to stderr. This can be disabled with 30 | * opterr, but the messages themselves are still inaccessible. 31 | * Optparse solves this by writing an error message in its errmsg 32 | * field. The downside to Optparse is that this error message will 33 | * always be in English rather than the current locale. 34 | * 35 | * Optparse should be familiar with anyone accustomed to getopt(), and 36 | * it could be a nearly drop-in replacement. The option string is the 37 | * same and the fields have the same names as the getopt() global 38 | * variables (optarg, optind, optopt). 39 | * 40 | * Optparse also supports GNU-style long options with optparse_long(). 41 | * The interface is slightly different and simpler than getopt_long(). 42 | * 43 | * By default, argv is permuted as it is parsed, moving non-option 44 | * arguments to the end. This can be disabled by setting the `permute` 45 | * field to 0 after initialization. 46 | */ 47 | #ifndef OPTPARSE_H 48 | #define OPTPARSE_H 49 | 50 | #ifndef OPTPARSE_API 51 | # define OPTPARSE_API 52 | #endif 53 | 54 | struct optparse { 55 | char **argv; 56 | int permute; 57 | int optind; 58 | int optopt; 59 | char *optarg; 60 | char errmsg[64]; 61 | int subopt; 62 | }; 63 | 64 | enum optparse_argtype { 65 | OPTPARSE_NONE, 66 | OPTPARSE_REQUIRED, 67 | OPTPARSE_OPTIONAL 68 | }; 69 | 70 | struct optparse_long { 71 | const char *longname; 72 | int shortname; 73 | enum optparse_argtype argtype; 74 | }; 75 | 76 | /** 77 | * Initializes the parser state. 78 | */ 79 | OPTPARSE_API 80 | void optparse_init(struct optparse *options, char **argv); 81 | 82 | /** 83 | * Read the next option in the argv array. 84 | * @param optstring a getopt()-formatted option string. 85 | * @return the next option character, -1 for done, or '?' for error 86 | * 87 | * Just like getopt(), a character followed by no colons means no 88 | * argument. One colon means the option has a required argument. Two 89 | * colons means the option takes an optional argument. 90 | */ 91 | OPTPARSE_API 92 | int optparse(struct optparse *options, const char *optstring); 93 | 94 | /** 95 | * Handles GNU-style long options in addition to getopt() options. 96 | * This works a lot like GNU's getopt_long(). The last option in 97 | * longopts must be all zeros, marking the end of the array. The 98 | * longindex argument may be NULL. 99 | */ 100 | OPTPARSE_API 101 | int optparse_long(struct optparse *options, 102 | const struct optparse_long *longopts, 103 | int *longindex); 104 | 105 | /** 106 | * Used for stepping over non-option arguments. 107 | * @return the next non-option argument, or NULL for no more arguments 108 | * 109 | * Argument parsing can continue with optparse() after using this 110 | * function. That would be used to parse the options for the 111 | * subcommand returned by optparse_arg(). This function allows you to 112 | * ignore the value of optind. 113 | */ 114 | OPTPARSE_API 115 | char *optparse_arg(struct optparse *options); 116 | 117 | /* Implementation */ 118 | #ifdef OPTPARSE_IMPLEMENTATION 119 | 120 | #define OPTPARSE_MSG_INVALID "invalid option" 121 | #define OPTPARSE_MSG_MISSING "option requires an argument" 122 | #define OPTPARSE_MSG_TOOMANY "option takes no arguments" 123 | 124 | static int 125 | optparse_error(struct optparse *options, const char *msg, const char *data) 126 | { 127 | unsigned p = 0; 128 | const char *sep = " -- '"; 129 | while (*msg) 130 | options->errmsg[p++] = *msg++; 131 | while (*sep) 132 | options->errmsg[p++] = *sep++; 133 | while (p < sizeof(options->errmsg) - 2 && *data) 134 | options->errmsg[p++] = *data++; 135 | options->errmsg[p++] = '\''; 136 | options->errmsg[p++] = '\0'; 137 | return '?'; 138 | } 139 | 140 | OPTPARSE_API 141 | void 142 | optparse_init(struct optparse *options, char **argv) 143 | { 144 | options->argv = argv; 145 | options->permute = 1; 146 | options->optind = 1; 147 | options->subopt = 0; 148 | options->optarg = 0; 149 | options->errmsg[0] = '\0'; 150 | } 151 | 152 | static int 153 | optparse_is_dashdash(const char *arg) 154 | { 155 | return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0'; 156 | } 157 | 158 | static int 159 | optparse_is_shortopt(const char *arg) 160 | { 161 | return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0'; 162 | } 163 | 164 | static int 165 | optparse_is_longopt(const char *arg) 166 | { 167 | return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0'; 168 | } 169 | 170 | static void 171 | optparse_permute(struct optparse *options, int index) 172 | { 173 | char *nonoption = options->argv[index]; 174 | int i; 175 | for (i = index; i < options->optind - 1; i++) 176 | options->argv[i] = options->argv[i + 1]; 177 | options->argv[options->optind - 1] = nonoption; 178 | } 179 | 180 | static int 181 | optparse_argtype(const char *optstring, char c) 182 | { 183 | int count = OPTPARSE_NONE; 184 | if (c == ':') 185 | return -1; 186 | for (; *optstring && c != *optstring; optstring++); 187 | if (!*optstring) 188 | return -1; 189 | if (optstring[1] == ':') 190 | count += optstring[2] == ':' ? 2 : 1; 191 | return count; 192 | } 193 | 194 | OPTPARSE_API 195 | int 196 | optparse(struct optparse *options, const char *optstring) 197 | { 198 | int type; 199 | char *next; 200 | char *option = options->argv[options->optind]; 201 | options->errmsg[0] = '\0'; 202 | options->optopt = 0; 203 | options->optarg = 0; 204 | if (option == 0) { 205 | return -1; 206 | } else if (optparse_is_dashdash(option)) { 207 | options->optind++; /* consume "--" */ 208 | return -1; 209 | } else if (!optparse_is_shortopt(option)) { 210 | if (options->permute) { 211 | int index = options->optind++; 212 | int r = optparse(options, optstring); 213 | optparse_permute(options, index); 214 | options->optind--; 215 | return r; 216 | } else { 217 | return -1; 218 | } 219 | } 220 | option += options->subopt + 1; 221 | options->optopt = option[0]; 222 | type = optparse_argtype(optstring, option[0]); 223 | next = options->argv[options->optind + 1]; 224 | switch (type) { 225 | case -1: { 226 | char str[2] = {0, 0}; 227 | str[0] = option[0]; 228 | options->optind++; 229 | return optparse_error(options, OPTPARSE_MSG_INVALID, str); 230 | } 231 | case OPTPARSE_NONE: 232 | if (option[1]) { 233 | options->subopt++; 234 | } else { 235 | options->subopt = 0; 236 | options->optind++; 237 | } 238 | return option[0]; 239 | case OPTPARSE_REQUIRED: 240 | options->subopt = 0; 241 | options->optind++; 242 | if (option[1]) { 243 | options->optarg = option + 1; 244 | } else if (next != 0) { 245 | options->optarg = next; 246 | options->optind++; 247 | } else { 248 | char str[2] = {0, 0}; 249 | str[0] = option[0]; 250 | options->optarg = 0; 251 | return optparse_error(options, OPTPARSE_MSG_MISSING, str); 252 | } 253 | return option[0]; 254 | case OPTPARSE_OPTIONAL: 255 | options->subopt = 0; 256 | options->optind++; 257 | if (option[1]) 258 | options->optarg = option + 1; 259 | else 260 | options->optarg = 0; 261 | return option[0]; 262 | } 263 | return 0; 264 | } 265 | 266 | OPTPARSE_API 267 | char * 268 | optparse_arg(struct optparse *options) 269 | { 270 | char *option = options->argv[options->optind]; 271 | options->subopt = 0; 272 | if (option != 0) 273 | options->optind++; 274 | return option; 275 | } 276 | 277 | static int 278 | optparse_longopts_end(const struct optparse_long *longopts, int i) 279 | { 280 | return !longopts[i].longname && !longopts[i].shortname; 281 | } 282 | 283 | static void 284 | optparse_from_long(const struct optparse_long *longopts, char *optstring) 285 | { 286 | char *p = optstring; 287 | int i; 288 | for (i = 0; !optparse_longopts_end(longopts, i); i++) { 289 | if (longopts[i].shortname) { 290 | int a; 291 | *p++ = longopts[i].shortname; 292 | for (a = 0; a < (int)longopts[i].argtype; a++) 293 | *p++ = ':'; 294 | } 295 | } 296 | *p = '\0'; 297 | } 298 | 299 | /* Unlike strcmp(), handles options containing "=". */ 300 | static int 301 | optparse_longopts_match(const char *longname, const char *option) 302 | { 303 | const char *a = option, *n = longname; 304 | if (longname == 0) 305 | return 0; 306 | for (; *a && *n && *a != '='; a++, n++) 307 | if (*a != *n) 308 | return 0; 309 | return *n == '\0' && (*a == '\0' || *a == '='); 310 | } 311 | 312 | /* Return the part after "=", or NULL. */ 313 | static char * 314 | optparse_longopts_arg(char *option) 315 | { 316 | for (; *option && *option != '='; option++); 317 | if (*option == '=') 318 | return option + 1; 319 | else 320 | return 0; 321 | } 322 | 323 | static int 324 | optparse_long_fallback(struct optparse *options, 325 | const struct optparse_long *longopts, 326 | int *longindex) 327 | { 328 | int result; 329 | char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */ 330 | optparse_from_long(longopts, optstring); 331 | result = optparse(options, optstring); 332 | if (longindex != 0) { 333 | *longindex = -1; 334 | if (result != -1) { 335 | int i; 336 | for (i = 0; !optparse_longopts_end(longopts, i); i++) 337 | if (longopts[i].shortname == options->optopt) 338 | *longindex = i; 339 | } 340 | } 341 | return result; 342 | } 343 | 344 | OPTPARSE_API 345 | int 346 | optparse_long(struct optparse *options, 347 | const struct optparse_long *longopts, 348 | int *longindex) 349 | { 350 | int i; 351 | char *option = options->argv[options->optind]; 352 | if (option == 0) { 353 | return -1; 354 | } else if (optparse_is_dashdash(option)) { 355 | options->optind++; /* consume "--" */ 356 | return -1; 357 | } else if (optparse_is_shortopt(option)) { 358 | return optparse_long_fallback(options, longopts, longindex); 359 | } else if (!optparse_is_longopt(option)) { 360 | if (options->permute) { 361 | int index = options->optind++; 362 | int r = optparse_long(options, longopts, longindex); 363 | optparse_permute(options, index); 364 | options->optind--; 365 | return r; 366 | } else { 367 | return -1; 368 | } 369 | } 370 | 371 | /* Parse as long option. */ 372 | options->errmsg[0] = '\0'; 373 | options->optopt = 0; 374 | options->optarg = 0; 375 | option += 2; /* skip "--" */ 376 | options->optind++; 377 | for (i = 0; !optparse_longopts_end(longopts, i); i++) { 378 | const char *name = longopts[i].longname; 379 | if (optparse_longopts_match(name, option)) { 380 | char *arg; 381 | if (longindex) 382 | *longindex = i; 383 | options->optopt = longopts[i].shortname; 384 | arg = optparse_longopts_arg(option); 385 | if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) { 386 | return optparse_error(options, OPTPARSE_MSG_TOOMANY, name); 387 | } if (arg != 0) { 388 | options->optarg = arg; 389 | } else if (longopts[i].argtype == OPTPARSE_REQUIRED) { 390 | options->optarg = options->argv[options->optind]; 391 | if (options->optarg == 0) 392 | return optparse_error(options, OPTPARSE_MSG_MISSING, name); 393 | else 394 | options->optind++; 395 | } 396 | return options->optopt; 397 | } 398 | } 399 | return optparse_error(options, OPTPARSE_MSG_INVALID, option); 400 | } 401 | 402 | #endif /* OPTPARSE_IMPLEMENTATION */ 403 | #endif /* OPTPARSE_H */ 404 | --------------------------------------------------------------------------------