diff options
-rw-r--r-- | Android.bp | 5 | ||||
-rw-r--r-- | CMakeLists.txt | 14 | ||||
-rw-r--r-- | examples/Makefile | 4 | ||||
-rw-r--r-- | include/tinyalsa/plugin.h | 154 | ||||
-rw-r--r-- | meson.build | 10 | ||||
-rw-r--r-- | src/Makefile | 8 | ||||
-rw-r--r-- | src/pcm.c | 121 | ||||
-rw-r--r-- | src/pcm_hw.c | 118 | ||||
-rw-r--r-- | src/pcm_io.h | 47 | ||||
-rw-r--r-- | src/pcm_plugin.c | 718 | ||||
-rw-r--r-- | src/snd_card_plugin.c | 149 | ||||
-rw-r--r-- | src/snd_card_plugin.h | 74 | ||||
-rw-r--r-- | utils/Makefile | 8 |
13 files changed, 1381 insertions, 49 deletions
@@ -5,6 +5,9 @@ cc_library { srcs: [ "src/mixer.c", "src/pcm.c", + "src/pcm_hw.c", + "src/pcm_plugin.c", + "src/snd_card_plugin.c", ], cflags: ["-Werror", "-Wno-macro-redefined"], export_include_dirs: ["include"], @@ -15,6 +18,8 @@ cc_library { enabled: false, }, }, + + system_shared_libs: ["libc", "libdl"], } cc_binary { diff --git a/CMakeLists.txt b/CMakeLists.txt index cb31c58..77227d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,24 +2,35 @@ cmake_minimum_required(VERSION 3.0.2) project("TinyALSA" C) +option(TINYALSA_USES_PLUGINS "Whether or not to build with plugin support" OFF) + +if (TINYALSA_USES_PLUGINS) + set (plugin_opt -DTINYALSA_USES_PLUGINS=1) +endif (TINYALSA_USES_PLUGINS) + set (HDRS "include/tinyalsa/attributes.h" "include/tinyalsa/version.h" "include/tinyalsa/asoundlib.h" "include/tinyalsa/pcm.h" + "include/tinyalsa/plugin.h" "include/tinyalsa/mixer.h") set (SRCS "src/pcm.c" + "src/pcm_hw.c" + "src/pcm_plugin.c" + "src/snd_card_plugin.c" "src/mixer.c") add_library("tinyalsa" ${HDRS} ${SRCS}) -target_compile_options("tinyalsa" PRIVATE -Wall -Wextra -Werror -Wfatal-errors) +target_compile_options("tinyalsa" PRIVATE -Wall -Wextra -Werror -Wfatal-errors ${plugin_opt}) target_include_directories("tinyalsa" PRIVATE "include") macro(ADD_EXAMPLE EXAMPLE) add_executable(${EXAMPLE} ${ARGN}) target_link_libraries(${EXAMPLE} "tinyalsa") + target_link_libraries(${EXAMPLE} "dl") target_include_directories(${EXAMPLE} PRIVATE "include") endmacro(ADD_EXAMPLE EXAMPLE) @@ -29,6 +40,7 @@ add_example("pcm-writei" "examples/pcm-writei.c") macro(ADD_UTIL UTIL) add_executable(${UTIL} ${ARGN}) target_link_libraries(${UTIL} "tinyalsa") + target_link_libraries(${UTIL} "dl") target_compile_options(${UTIL} PRIVATE -Wall -Wextra -Werror -Wfatal-errors) target_include_directories(${UTIL} PRIVATE "include") endmacro(ADD_UTIL UTIL) diff --git a/examples/Makefile b/examples/Makefile index 807d4c8..c52d367 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -11,9 +11,9 @@ EXAMPLES += pcm-writei .PHONY: all all: $(EXAMPLES) -pcm-readi: pcm-readi.c -ltinyalsa +pcm-readi: pcm-readi.c -ltinyalsa -ldl -pcm-writei: pcm-writei.c -ltinyalsa +pcm-writei: pcm-writei.c -ltinyalsa -ldl .PHONY: clean clean: diff --git a/include/tinyalsa/plugin.h b/include/tinyalsa/plugin.h new file mode 100644 index 0000000..87bdaa0 --- /dev/null +++ b/include/tinyalsa/plugin.h @@ -0,0 +1,154 @@ +/* plugin.h +** Copyright (c) 2019-2020, The Linux Foundation. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above +** copyright notice, this list of conditions and the following +** disclaimer in the documentation and/or other materials provided +** with the distribution. +** * Neither the name of The Linux Foundation nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**/ + +#ifndef TINYALSA_PLUGIN_H +#define TINYALSA_PLUGIN_H + +#include <stdint.h> +#include <stdlib.h> + +#include <sound/asound.h> + * entry point function. + * @ingroup libtinyalsa-pcm + */ +#define PCM_PLUGIN_OPEN_FN_PTR() \ + int (*plugin_open_fn) (struct pcm_plugin **plugin, \ + unsigned int card, \ + unsigned int device, \ + int mode); +/** Forward declaration of the structure to hold pcm + * plugin related data/information. + */ +struct pcm_plugin; +struct snd_node; + +/** Operations that are required to be registered by the plugin. + * @ingroup libtinyalsa-pcm + */ +struct pcm_plugin_ops { + /** open the pcm plugin */ + int (*open) (struct pcm_plugin **plugin, unsigned int card, + unsigned int device, unsigned int flags); + /** close the pcm plugin */ + int (*close) (struct pcm_plugin *plugin); + /** Set the PCM hardware parameters to the plugin */ + int (*hw_params) (struct pcm_plugin *plugin, + struct snd_pcm_hw_params *params); + /** Set the PCM software parameters to the plugin */ + int (*sw_params) (struct pcm_plugin *plugin, + struct snd_pcm_sw_params *params); + /** Synchronize the pointer */ + int (*sync_ptr) (struct pcm_plugin *plugin, + struct snd_pcm_sync_ptr *sync_ptr); + /** Write frames to plugin to be rendered to output */ + int (*writei_frames) (struct pcm_plugin *plugin, + struct snd_xferi *x); + /** Read frames from plugin captured from input */ + int (*readi_frames) (struct pcm_plugin *plugin, + struct snd_xferi *x); + /** Obtain the timestamp for the PCM */ + int (*ttstamp) (struct pcm_plugin *plugin, + int *tstamp); + /** Prepare the plugin for data transfer */ + int (*prepare) (struct pcm_plugin *plugin); + /** Start data transfer from/to the plugin */ + int (*start) (struct pcm_plugin *plugin); + /** Drop pcm frames */ + int (*drop) (struct pcm_plugin *plugin); + /** Any custom or alsa specific ioctl implementation */ + int (*ioctl) (struct pcm_plugin *plugin, + int cmd, void *arg); +}; + +/** Minimum and maximum values for hardware parameter constraints. + * @ingroup libtinyalsa-pcm + */ +struct pcm_plugin_min_max { + /** Minimum value for the hardware parameter */ + unsigned int min; + /** Maximum value for the hardware parameter */ + unsigned int max; +}; + +/** Encapsulate the hardware parameter constraints + * @ingroup libtinyalsa-pcm + */ +struct pcm_plugin_hw_constraints { + /** Value for SNDRV_PCM_HW_PARAM_ACCESS param */ + uint64_t access; + /** Value for SNDRV_PCM_HW_PARAM_FORMAT param. + * As of this implementation ALSA supports 52 formats */ + uint64_t format; + /** Value for SNDRV_PCM_HW_PARAM_SAMPLE_BITS param */ + struct pcm_plugin_min_max bit_width; + /** Value for SNDRV_PCM_HW_PARAM_CHANNELS param */ + struct pcm_plugin_min_max channels; + /** Value for SNDRV_PCM_HW_PARAM_RATE param */ + struct pcm_plugin_min_max rate; + /** Value for SNDRV_PCM_HW_PARAM_PERIODS param */ + struct pcm_plugin_min_max periods; + /** Value for SNDRV_PCM_HW_PARAM_PERIOD_BYTES param */ + struct pcm_plugin_min_max period_bytes; +}; + +struct pcm_plugin { + /** Card number for the pcm device */ + unsigned int card; + /** device number for the pcm device */ + unsigned int device; + /** pointer to the contraints registered by the plugin */ + struct pcm_plugin_hw_constraints *constraints; + /** Indicates read/write mode, etc.. */ + int mode; + /* Pointer to hold the plugin's private data */ + void *priv; + /* Tracks the plugin state */ + unsigned int state; +}; + + +/** Operations defined by the plugin. + * */ +struct snd_node_ops { + /** Function pointer to get card definition */ + void* (*open_card)(unsigned int card); + /** Function pointer to release card definition */ + void (*close_card)(void *card); + /** Get interger type properties from device definition */ + int (*get_int)(void *node, const char *prop, int *val); + /** Get string type properties from device definition */ + int (*get_str)(void *node, const char *prop, char **val); + /** Function pointer to get mixer definition */ + void* (*get_mixer)(void *card); + /** Function pointer to get PCM definition */ + void* (*get_pcm)(void *card, unsigned int id); + /** Reserved for other nodes such as compress */ + void* reserved[4]; +}; +#endif /* end of TINYALSA_PLUGIN_H */ diff --git a/meson.build b/meson.build index abbca8e..4510ef0 100644 --- a/meson.build +++ b/meson.build @@ -4,11 +4,17 @@ project ('tinyalsa', 'c', tinyalsa_includes = include_directories('.', 'include') +cc = meson.get_compiler('c') + +# Dependency on libdl +dl_dep = cc.find_library('dl') + tinyalsa = library('tinyalsa', - 'src/mixer.c', 'src/pcm.c', + 'src/mixer.c', 'src/pcm.c', 'src/pcm_hw.c', 'src/pcm_plugin.c', 'src/snd_card_plugin.c', include_directories: tinyalsa_includes, version: meson.project_version(), - install: true) + install: true, + dependencies: dl_dep) # For use as a Meson subproject tinyalsa_dep = declare_dependency(link_with: tinyalsa, diff --git a/src/Makefile b/src/Makefile index aa00484..b278f47 100644 --- a/src/Makefile +++ b/src/Makefile @@ -15,7 +15,7 @@ INCLUDE_DIRS = -I ../include override CFLAGS := $(WARNINGS) $(INCLUDE_DIRS) -fPIC $(CFLAGS) VPATH = ../include/tinyalsa -OBJECTS = limits.o mixer.o pcm.o +OBJECTS = limits.o mixer.o pcm.o pcm_plugin.o pcm_hw.o snd_card_plugin.o LIBVERSION_MAJOR = $(TINYALSA_VERSION_MAJOR) LIBVERSION = $(TINYALSA_VERSION) @@ -25,10 +25,16 @@ all: libtinyalsa.a libtinyalsa.so pcm.o: pcm.c pcm.h +pcm_plugin.o: pcm_plugin.c pcm_io.h + +pcm_hw.o: pcm_hw.c pcm_io.h + limits.o: limits.c limits.h mixer.o: mixer.c mixer.h +snd_card_plugin.o: snd_card_plugin.c snd_card_plugin.h + libtinyalsa.a: $(OBJECTS) $(AR) $(ARFLAGS) $@ $^ @@ -59,6 +59,8 @@ #include <tinyalsa/pcm.h> #include <tinyalsa/limits.h> +#include "pcm_io.h" +#include "snd_card_plugin.h" #ifndef PARAM_MAX #define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL @@ -68,6 +70,7 @@ #define SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP (1<<2) #endif /* SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP */ + static inline int param_is_mask(int p) { return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) && @@ -234,6 +237,12 @@ struct pcm { long pcm_delay; /** The subdevice corresponding to the PCM */ unsigned int subdevice; + /** Pointer to the pcm ops, either hw or plugin */ + const struct pcm_ops *ops; + /** Private data for pcm_hw or pcm_plugin */ + void *data; + /** Pointer to the pcm node from snd card definition */ + struct snd_node *snd_node; }; static int oops(struct pcm *pcm, int e, const char *fmt, ...) @@ -383,7 +392,7 @@ int pcm_set_config(struct pcm *pcm, const struct pcm_config *config) param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS, SNDRV_PCM_ACCESS_RW_INTERLEAVED); - if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) { + if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) { int errno_copy = errno; oops(pcm, -errno, "cannot set hw params"); return -errno_copy; @@ -440,7 +449,7 @@ int pcm_set_config(struct pcm *pcm, const struct pcm_config *config) while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size) pcm->boundary *= 2; - if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) { + if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) { int errno_copy = errno; oops(pcm, -errno, "cannot set sw params"); return -errno_copy; @@ -519,7 +528,8 @@ static int pcm_sync_ptr(struct pcm *pcm, int flags) } } else { pcm->sync_ptr->flags = flags; - if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SYNC_PTR, pcm->sync_ptr) < 0) { + if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SYNC_PTR, + pcm->sync_ptr) < 0) { oops(pcm, errno, "failed to sync mmap ptr"); return -1; } @@ -599,19 +609,30 @@ struct pcm_params *pcm_params_get(unsigned int card, unsigned int device, unsigned int flags) { struct snd_pcm_hw_params *params; - char fn[256]; + void *snd_node = NULL, *data; + const struct pcm_ops *ops; int fd; - snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, - flags & PCM_IN ? 'c' : 'p'); - - if (flags & PCM_NONBLOCK) - fd = open(fn, O_RDWR | O_NONBLOCK); - else - fd = open(fn, O_RDWR); + ops = &hw_ops; + fd = ops->open(card, device, flags, &data, snd_node); +#ifdef TINYALSA_USES_PLUGINS + if (fd < 0) { + int pcm_type; + snd_node = snd_utils_open_pcm(card, device); + pcm_type = snd_utils_get_node_type(snd_node); + if (!snd_node || pcm_type != SND_NODE_TYPE_PLUGIN) { + fprintf(stderr, "no device (hw/plugin) for card(%u), device(%u)", + card, device); + goto err_open; + } + ops = &plug_ops; + fd = ops->open(card, device, flags, &data, snd_node); + } +#endif if (fd < 0) { - fprintf(stderr, "cannot open device '%s': %s\n", fn, strerror(errno)); + fprintf(stderr, "cannot open card(%d) device (%d): %s\n", + card, device, strerror(errno)); goto err_open; } @@ -620,19 +641,27 @@ struct pcm_params *pcm_params_get(unsigned int card, unsigned int device, goto err_calloc; param_init(params); - if (ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, params)) { + if (ops->ioctl(data, SNDRV_PCM_IOCTL_HW_REFINE, params)) { fprintf(stderr, "SNDRV_PCM_IOCTL_HW_REFINE error (%d)\n", errno); goto err_hw_refine; } - close(fd); +#ifdef TINYALSA_USES_PLUGINS + if (snd_node) + snd_utils_close_dev_node(snd_node); +#endif + ops->close(data); return (struct pcm_params *)params; err_hw_refine: free(params); err_calloc: - close(fd); +#ifdef TINYALSA_USES_PLUGINS + if (snd_node) + snd_utils_close_dev_node(snd_node); +#endif + ops->close(data); err_open: return NULL; } @@ -787,8 +816,8 @@ int pcm_close(struct pcm *pcm) munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size)); } - if (pcm->fd >= 0) - close(pcm->fd); + snd_utils_close_dev_node(pcm->snd_node); + pcm->ops->close(pcm->data); pcm->buffer_size = 0; pcm->fd = -1; free(pcm); @@ -851,29 +880,39 @@ struct pcm *pcm_open(unsigned int card, unsigned int device, { struct pcm *pcm; struct snd_pcm_info info; - char fn[256]; int rc; pcm = calloc(1, sizeof(struct pcm)); if (!pcm) return &bad_pcm; - snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, - flags & PCM_IN ? 'c' : 'p'); - - pcm->flags = flags; - - if (flags & PCM_NONBLOCK) - pcm->fd = open(fn, O_RDWR | O_NONBLOCK); - else - pcm->fd = open(fn, O_RDWR); + /* Default to hw_ops, attemp plugin open only if hw (/dev/snd/pcm*) open fails */ + pcm->ops = &hw_ops; + pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, NULL); +#ifdef TINYALSA_USES_PLUGINS + if (pcm->fd < 0) { + int pcm_type; + pcm->snd_node = snd_utils_open_pcm(card, device); + pcm_type = snd_utils_get_node_type(pcm->snd_node); + if (!pcm->snd_node || pcm_type != SND_NODE_TYPE_PLUGIN) { + oops(pcm, -ENODEV, "no device (hw/plugin) for card(%u), device(%u)", + card, device); + return pcm; + } + pcm->ops = &plug_ops; + pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, pcm->snd_node); + } +#endif if (pcm->fd < 0) { - oops(pcm, errno, "cannot open device '%s'", fn); + oops(pcm, errno, "cannot open device (%u) for card (%u)", + device, card); return pcm; } - if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) { + pcm->flags = flags; + + if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_INFO, &info)) { oops(pcm, errno, "cannot get info"); goto fail_close; } @@ -891,7 +930,7 @@ struct pcm *pcm_open(unsigned int card, unsigned int device, #ifdef SNDRV_PCM_IOCTL_TTSTAMP if (pcm->flags & PCM_MONOTONIC) { int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC; - rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg); + rc = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_TTSTAMP, &arg); if (rc < 0) { oops(pcm, rc, "cannot set timestamp type"); goto fail; @@ -911,7 +950,11 @@ fail: if (flags & PCM_MMAP) munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size)); fail_close: - close(pcm->fd); +#ifdef TINYALSA_USES_PLUGINS + if (pcm->snd_node) + snd_utils_close_dev_node(pcm->snd_node); +#endif + pcm->ops->close(pcm->data); pcm->fd = -1; return pcm; } @@ -970,7 +1013,7 @@ int pcm_unlink(struct pcm *pcm) */ int pcm_prepare(struct pcm *pcm) { - if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) < 0) + if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_PREPARE) < 0) return oops(pcm, errno, "cannot prepare channel"); /* get appl_ptr and avail_min from kernel */ @@ -991,7 +1034,7 @@ int pcm_start(struct pcm *pcm) return -1; if (pcm->mmap_status->state != PCM_STATE_RUNNING) { - if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START) < 0) + if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_START) < 0) return oops(pcm, errno, "cannot start channel"); } @@ -1005,7 +1048,7 @@ int pcm_start(struct pcm *pcm) */ int pcm_stop(struct pcm *pcm) { - if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP) < 0) + if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_DROP) < 0) return oops(pcm, errno, "cannot stop channel"); return 0; @@ -1288,7 +1331,7 @@ int pcm_mmap_transfer(struct pcm *pcm, void *buffer, unsigned int frames) */ if (!is_playback && state == PCM_STATE_PREPARED && frames >= pcm->config.start_threshold) { - err = ioctl(pcm->fd, SNDRV_PCM_IOCTL_START); + err = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_START); if (err == -1) return -1; /* state = PCM_STATE_RUNNING */ @@ -1326,7 +1369,7 @@ int pcm_mmap_transfer(struct pcm *pcm, void *buffer, unsigned int frames) /* start playback if written >= start_threshold */ if (is_playback && state == PCM_STATE_PREPARED && pcm->buffer_size - avail >= pcm->config.start_threshold) { - err = ioctl(pcm->fd, SNDRV_PCM_IOCTL_START); + err = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_START); if (err == -1) break; } @@ -1365,9 +1408,9 @@ static int pcm_rw_transfer(struct pcm *pcm, void *data, unsigned int frames) transfer.frames = frames; transfer.result = 0; - res = ioctl(pcm->fd, is_playback - ? SNDRV_PCM_IOCTL_WRITEI_FRAMES - : SNDRV_PCM_IOCTL_READI_FRAMES, &transfer); + res = pcm->ops->ioctl(pcm->data, is_playback + ? SNDRV_PCM_IOCTL_WRITEI_FRAMES + : SNDRV_PCM_IOCTL_READI_FRAMES, &transfer); return res == 0 ? (int) transfer.result : -1; } diff --git a/src/pcm_hw.c b/src/pcm_hw.c new file mode 100644 index 0000000..1e67e9b --- /dev/null +++ b/src/pcm_hw.c @@ -0,0 +1,118 @@ +/* pcm_hw.c +** Copyright (c) 2019, The Linux Foundation. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above +** copyright notice, this list of conditions and the following +** disclaimer in the documentation and/or other materials provided +** with the distribution. +** * Neither the name of The Linux Foundation nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**/ + +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <poll.h> + +#include <sys/ioctl.h> +#include <linux/ioctl.h> +#include <sound/asound.h> +#include <tinyalsa/asoundlib.h> + +#include "pcm_io.h" + +struct pcm_hw_data { + /** Card number of the pcm device */ + unsigned int card; + /** Device number for the pcm device */ + unsigned int device; + /** File descriptor to the pcm device file node */ + unsigned int fd; + /** Pointer to the pcm node from snd card definiton */ + struct snd_node *node; +}; + +static void pcm_hw_close(void *data) +{ + struct pcm_hw_data *hw_data = data; + + if (hw_data->fd > 0) + close(hw_data->fd); + + free(hw_data); +} + +static int pcm_hw_ioctl(void *data, unsigned int cmd, ...) +{ + struct pcm_hw_data *hw_data = data; + va_list ap; + void *arg; + + va_start(ap, cmd); + arg = va_arg(ap, void *); + va_end(ap); + + return ioctl(hw_data->fd, cmd, arg); +} + +static int pcm_hw_open(unsigned int card, unsigned int device, + unsigned int flags, void **data, struct snd_node *node) +{ + struct pcm_hw_data *hw_data; + char fn[256]; + int fd; + + hw_data = calloc(1, sizeof(*hw_data)); + if (!hw_data) { + return -ENOMEM; + } + + snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device, + flags & PCM_IN ? 'c' : 'p'); + if (flags & PCM_NONBLOCK) + fd = open(fn, O_RDWR|O_NONBLOCK); + else + fd = open(fn, O_RDWR); + + if (fd < 0) { + return fd; + } + + hw_data->card = card; + hw_data->device = device; + hw_data->fd = fd; + hw_data->node = node; + + *data = hw_data; + + return fd; +} + +const struct pcm_ops hw_ops = { + .open = pcm_hw_open, + .close = pcm_hw_close, + .ioctl = pcm_hw_ioctl, +}; + diff --git a/src/pcm_io.h b/src/pcm_io.h new file mode 100644 index 0000000..1b68ac8 --- /dev/null +++ b/src/pcm_io.h @@ -0,0 +1,47 @@ +/* pcm_io.h +** Copyright (c) 2019, The Linux Foundation. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above +** copyright notice, this list of conditions and the following +** disclaimer in the documentation and/or other materials provided +** with the distribution. +** * Neither the name of The Linux Foundation nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**/ + +#ifndef TINYALSA_SRC_PCM_IO_H +#define TINYALSA_SRC_PCM_IO_H + +#include <sound/asound.h> + +struct snd_node; + +struct pcm_ops { + int (*open) (unsigned int card, unsigned int device, + unsigned int flags, void **data, struct snd_node *node); + void (*close) (void *data); + int (*ioctl) (void *data, unsigned int cmd, ...); +}; + +extern const struct pcm_ops hw_ops; +extern const struct pcm_ops plug_ops; + +#endif /* TINYALSA_SRC_PCM_IO_H */ diff --git a/src/pcm_plugin.c b/src/pcm_plugin.c new file mode 100644 index 0000000..695ad2c --- /dev/null +++ b/src/pcm_plugin.c @@ -0,0 +1,718 @@ +/* pcm_plugin.c +** Copyright (c) 2019, The Linux Foundation. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above +** copyright notice, this list of conditions and the following +** disclaimer in the documentation and/or other materials provided +** with the distribution. +** * Neither the name of The Linux Foundation nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**/ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <fcntl.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <poll.h> +#include <dlfcn.h> + +#include <sys/ioctl.h> +#include <linux/ioctl.h> +#include <sound/asound.h> +#include <tinyalsa/asoundlib.h> +#include <tinyalsa/plugin.h> + +#include "pcm_io.h" +#include "snd_card_plugin.h" + +/* 2 words of uint32_t = 64 bits of mask */ +#define PCM_MASK_SIZE (2) +#define ARRAY_SIZE(a) \ + (sizeof(a) / sizeof(a[0])) + +#define PCM_PARAM_GET_MASK(p, n) \ + &p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK]; + +enum { + PCM_PLUG_HW_PARAM_SELECT_MIN, + PCM_PLUG_HW_PARAM_SELECT_MAX, + PCM_PLUG_HW_PARAM_SELECT_VAL, +}; + +enum { + PCM_PLUG_STATE_OPEN, + PCM_PLUG_STATE_SETUP, + PCM_PLUG_STATE_PREPARED, + PCM_PLUG_STATE_RUNNING, +}; + +struct pcm_plug_data { + unsigned int card; + unsigned int device; + unsigned int fd; + unsigned int flags; + + void *dl_hdl; + /** pointer to plugin operation */ + const struct pcm_plugin_ops *ops; + struct pcm_plugin *plugin; + void *dev_node; +}; + +static unsigned int param_list[] = { + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + SNDRV_PCM_HW_PARAM_CHANNELS, + SNDRV_PCM_HW_PARAM_RATE, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + SNDRV_PCM_HW_PARAM_PERIODS, +}; + +static void pcm_plug_close(void *data) +{ + struct pcm_plug_data *plug_data = data; + struct pcm_plugin *plugin = plug_data->plugin; + + plug_data->ops->close(plugin); + dlclose(plug_data->dl_hdl); + + free(plug_data); +} + +static int pcm_plug_info(struct pcm_plug_data *plug_data, + struct snd_pcm_info *info) +{ + int stream = SNDRV_PCM_STREAM_PLAYBACK; + int ret = 0, val = -1; + char *name; + + memset(info, 0, sizeof(*info)); + + if (plug_data->flags & PCM_IN) { + stream = SNDRV_PCM_STREAM_CAPTURE; + ret = snd_utils_get_int(plug_data->dev_node, "capture", &val); + if (ret || !val) { + fprintf(stderr, "%s: not a capture device\n", __func__); + return -EINVAL; + } + } else { + stream = SNDRV_PCM_STREAM_PLAYBACK; + ret = snd_utils_get_int(plug_data->dev_node, "playback", &val); + if (ret || !val) { + fprintf(stderr, "%s: not a playback device\n", __func__); + return -EINVAL; + } + } + + info->stream = stream; + info->card = plug_data->card; + info->device = plug_data->device; + + ret = snd_utils_get_str(plug_data->dev_node, "name", &name); + if (ret) { + fprintf(stderr, "%s: failed to get pcm device name\n", __func__); + return ret; + } + + strncpy((char *)info->id, name, sizeof(info->id)); + strncpy((char *)info->name, name, sizeof(info->name)); + strncpy((char *)info->subname, name, sizeof(info->subname)); + + info->subdevices_count = 1; + + return ret; +} + +static void pcm_plug_set_mask(struct snd_pcm_hw_params *p, int n, uint64_t v) +{ + struct snd_mask *mask; + + mask = PCM_PARAM_GET_MASK(p, n); + + mask->bits[0] |= (v & 0xFFFFFFFF); + mask->bits[1] |= ((v >> 32) & 0xFFFFFFFF); + /* + * currently only supporting 64 bits, may need to update to support + * more than 64 bits + */ +} + +static void pcm_plug_set_interval(struct snd_pcm_hw_params *params, + int p, struct pcm_plugin_min_max *v, int is_integer) +{ + struct snd_interval *i; + + i = ¶ms->intervals[p - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]; + + i->min = v->min; + i->max = v->max; + + if (is_integer) + i->integer = 1; +} + +static int pcm_plug_frames_to_bytes(unsigned int frames, + unsigned int frame_bits) +{ + return (frames * (frame_bits / 8)); +} + +static int pcm_plug_bytes_to_frames(unsigned int size, + unsigned int frame_bits) +{ + return (size * 8) / frame_bits; +} + +static int pcm_plug_get_params(struct pcm_plugin *plugin, + struct snd_pcm_hw_params *params) +{ + struct pcm_plugin_min_max bw, ch, pb, periods; + struct pcm_plugin_min_max val; + struct pcm_plugin_min_max frame_bits, buffer_bytes; + + /* + * populate the struct snd_pcm_hw_params structure + * using the hw_param constraints provided by plugin + * via the plugin->constraints + */ + + /* Set the mask params */ + pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_ACCESS, + plugin->constraints->access); + pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_FORMAT, + plugin->constraints->format); + pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT, + SNDRV_PCM_SUBFORMAT_STD); + + /* Set the standard interval params */ + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + &plugin->constraints->bit_width, 1); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS, + &plugin->constraints->channels, 1); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_RATE, + &plugin->constraints->rate, 1); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + &plugin->constraints->period_bytes, 0); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIODS, + &plugin->constraints->periods, 1); + + /* set the calculated interval params */ + + bw.min = plugin->constraints->bit_width.min; + bw.max = plugin->constraints->bit_width.max; + + ch.min = plugin->constraints->channels.min; + ch.max = plugin->constraints->channels.max; + + pb.min = plugin->constraints->period_bytes.min; + pb.max = plugin->constraints->period_bytes.max; + + periods.min = plugin->constraints->periods.min; + periods.max = plugin->constraints->periods.max; + + /* Calculate and set frame bits */ + frame_bits.min = bw.min * ch.min; + frame_bits.max = bw.max * ch.max; + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS, + &frame_bits, 1); + + + /* Calculate and set period_size in frames */ + val.min = pcm_plug_bytes_to_frames(pb.min, frame_bits.min); + val.max = pcm_plug_bytes_to_frames(pb.max, frame_bits.min); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + &val, 1); + + /* Calculate and set buffer_bytes */ + buffer_bytes.min = pb.min * periods.min; + buffer_bytes.max = pb.max * periods.max; + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + &buffer_bytes, 1); + + /* Calculate and set buffer_size in frames */ + val.min = pcm_plug_bytes_to_frames(buffer_bytes.min, frame_bits.min); + val.max = pcm_plug_bytes_to_frames(buffer_bytes.max, frame_bits.min); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + &val, 1); + return 0; +} + +static int pcm_plug_masks_refine(struct snd_pcm_hw_params *p, + struct snd_pcm_hw_params *c) +{ + struct snd_mask *req_mask; + struct snd_mask *con_mask; + unsigned int idx, i, masks; + + masks = SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK; + + for (idx = 0; idx <= masks; idx++) { + + if (!(p->rmask & (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_MASK)))) + continue; + + req_mask = PCM_PARAM_GET_MASK(p, idx); + con_mask = PCM_PARAM_GET_MASK(c, idx); + + /* + * set the changed mask if requested mask value is not the same as + * constrained mask value + */ + if (memcmp(req_mask, con_mask, PCM_MASK_SIZE * sizeof(uint32_t))) + p->cmask |= 1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_MASK); + + /* Actually change the requested mask to constrained mask */ + for (i = 0; i < PCM_MASK_SIZE; i++) + req_mask->bits[i] &= con_mask->bits[i]; + } + + return 0; +} + +static int pcm_plug_interval_refine(struct snd_pcm_hw_params *p, + struct snd_pcm_hw_params *c) +{ + struct snd_interval *ri; + struct snd_interval *ci; + unsigned int idx; + unsigned int intervals; + int changed = 0; + + intervals = SNDRV_PCM_HW_PARAM_LAST_INTERVAL - + SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; + + for (idx = 0; idx <= intervals; idx++) { + ri = &p->intervals[idx]; + ci = &c->intervals[idx]; + + if (!(p->rmask & (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_INTERVAL)) )) + continue; + + if (ri->min < ci->min) { + ri->min = ci->min; + ri->openmin = ci->openmin; + changed = 1; + } else if (ri->min == ci->min && !ri->openmin && ci->openmin) { + ri->openmin = 1; + changed = 1; + } + + if (ri->max > ci->max) { + ri->max = ci->max; + ri->openmax = ci->openmax; + changed = 1; + } else if (ri->max == ci->max && !ri->openmax && ci->openmax) { + ri->openmax = 1; + changed = 1; + }; + + if (!ri->integer && ci->integer) { + ri->integer = 1; + changed = 1; + } + + if (ri->integer) { + if (ri->openmin) { + ri->min++; + ri->openmin = 0; + } + if (ri->openmax) { + ri->max--; + ri->openmax = 0; + } + } else if (!ri->openmin && !ri->openmax && ri->min == ri->max) { + ri->integer = 1; + } + + /* Set the changed mask */ + if (changed) + p->cmask |= (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_INTERVAL)); + } + + return 0; +} + + +static int pcm_plug_hw_params_refine(struct snd_pcm_hw_params *p, + struct snd_pcm_hw_params *c) +{ + int rc; + + rc = pcm_plug_masks_refine(p, c); + if (rc) { + fprintf(stderr, "%s: masks refine failed %d\n", __func__, rc); + return rc; + } + + rc = pcm_plug_interval_refine(p, c); + if (rc) { + fprintf(stderr, "%s: interval refine failed %d\n", __func__, rc); + return rc; + } + + /* clear the requested params */ + p->rmask = 0; + + return rc; +} + +static int __pcm_plug_hrefine(struct pcm_plug_data *plug_data, + struct snd_pcm_hw_params *params) +{ + struct pcm_plugin *plugin = plug_data->plugin; + struct snd_pcm_hw_params plug_params; + int rc; + + memset(&plug_params, 0, sizeof(plug_params)); + rc = pcm_plug_get_params(plugin, &plug_params); + if (rc) { + fprintf(stderr, "%s: pcm_plug_get_params failed %d\n", + __func__, rc); + return -EINVAL; + } + + return pcm_plug_hw_params_refine(params, &plug_params); + +} + +static int pcm_plug_hrefine(struct pcm_plug_data *plug_data, + struct snd_pcm_hw_params *params) +{ + return __pcm_plug_hrefine(plug_data, params); +} + +static int pcm_plug_interval_select(struct snd_pcm_hw_params *p, + unsigned int param, unsigned int select, unsigned int val) +{ + struct snd_interval *i; + + if (param < SNDRV_PCM_HW_PARAM_FIRST_INTERVAL || + param > SNDRV_PCM_HW_PARAM_LAST_INTERVAL) + return -EINVAL; + + i = &p->intervals[param - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]; + + if (!i->min) + return -EINVAL; + + switch (select) { + + case PCM_PLUG_HW_PARAM_SELECT_MIN: + i->max = i->min; + break; + + case PCM_PLUG_HW_PARAM_SELECT_MAX: + i->min = i->max; + break; + + case PCM_PLUG_HW_PARAM_SELECT_VAL: + i->min = i->max = val; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static void pcm_plug_hw_params_set(struct snd_pcm_hw_params *p) +{ + unsigned int i, select; + unsigned int bw, ch, period_sz, periods; + unsigned int val1, val2, offset; + + offset = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; + + /* Select the min values first */ + select = PCM_PLUG_HW_PARAM_SELECT_MIN; + for (i = 0; i < ARRAY_SIZE(param_list); i++) + pcm_plug_interval_select(p, param_list[i], select, 0); + + /* Select calculated values */ + select = PCM_PLUG_HW_PARAM_SELECT_VAL; + bw = (p->intervals[SNDRV_PCM_HW_PARAM_SAMPLE_BITS - offset]).min; + ch = (p->intervals[SNDRV_PCM_HW_PARAM_CHANNELS - offset]).min; + period_sz = (p->intervals[SNDRV_PCM_HW_PARAM_PERIOD_SIZE - offset]).min; + periods = (p->intervals[SNDRV_PCM_HW_PARAM_PERIODS - offset]).min; + + val1 = bw * ch; // frame_bits; + pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_FRAME_BITS, select, val1); + + val2 = pcm_plug_frames_to_bytes(period_sz, val1); // period_bytes; + pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, select, + val2); + + val2 = period_sz * periods; //buffer_size; + pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, select, val2); + + val2 = pcm_plug_frames_to_bytes(period_sz * periods, val1); //buffer_bytes; + pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, select, val2); +} + +static int pcm_plug_hparams(struct pcm_plug_data *plug_data, + struct snd_pcm_hw_params *params) +{ + struct pcm_plugin *plugin = plug_data->plugin; + int rc; + + if (plugin->state != PCM_PLUG_STATE_OPEN) + return -EBADFD; + + params->rmask = ~0U; + + rc = __pcm_plug_hrefine(plug_data, params); + if (rc) { + fprintf(stderr, "%s: __pcm_plug_hrefine failed %d\n", + __func__, rc); + return rc; + } + + pcm_plug_hw_params_set(params); + + rc = plug_data->ops->hw_params(plugin, params); + if (!rc) + plugin->state = PCM_PLUG_STATE_SETUP; + + return rc; +} + +static int pcm_plug_sparams(struct pcm_plug_data *plug_data, + struct snd_pcm_sw_params *params) +{ + struct pcm_plugin *plugin = plug_data->plugin; + + if (plugin->state != PCM_PLUG_STATE_SETUP) + return -EBADFD; + + return plug_data->ops->sw_params(plugin, params); +} + +static int pcm_plug_sync_ptr(struct pcm_plug_data *plug_data, + struct snd_pcm_sync_ptr *sync_ptr) +{ + struct pcm_plugin *plugin = plug_data->plugin; + + return plug_data->ops->sync_ptr(plugin, sync_ptr); +} + +static int pcm_plug_writei_frames(struct pcm_plug_data *plug_data, + struct snd_xferi *x) +{ + struct pcm_plugin *plugin = plug_data->plugin; + + if (plugin->state != PCM_PLUG_STATE_PREPARED && + plugin->state != PCM_PLUG_STATE_RUNNING) + return -EBADFD; + + return plug_data->ops->writei_frames(plugin, x); +} + +static int pcm_plug_readi_frames(struct pcm_plug_data *plug_data, + struct snd_xferi *x) +{ + struct pcm_plugin *plugin = plug_data->plugin; + + if (plugin->state != PCM_PLUG_STATE_RUNNING) + return -EBADFD; + + return plug_data->ops->readi_frames(plugin, x); +} + +static int pcm_plug_ttstamp(struct pcm_plug_data *plug_data, + int *tstamp) +{ + struct pcm_plugin *plugin = plug_data->plugin; + + if (plugin->state != PCM_PLUG_STATE_RUNNING) + return -EBADFD; + + return plug_data->ops->ttstamp(plugin, tstamp); +} + +static int pcm_plug_prepare(struct pcm_plug_data *plug_data) +{ + struct pcm_plugin *plugin = plug_data->plugin; + int rc; + + if (plugin->state != PCM_PLUG_STATE_SETUP) + return -EBADFD; + + rc = plug_data->ops->prepare(plugin); + if (!rc) + plugin->state = PCM_PLUG_STATE_PREPARED; + + return rc; +} + +static int pcm_plug_start(struct pcm_plug_data *plug_data) +{ + struct pcm_plugin *plugin = plug_data->plugin; + int rc; + + if (plugin->state != PCM_PLUG_STATE_PREPARED) + return -EBADFD; + + rc = plug_data->ops->start(plugin); + if (!rc) + plugin->state = PCM_PLUG_STATE_RUNNING; + + return rc; +} + +static int pcm_plug_drop(struct pcm_plug_data *plug_data) +{ + struct pcm_plugin *plugin = plug_data->plugin; + int rc; + + rc = plug_data->ops->drop(plugin); + if (!rc) + plugin->state = PCM_PLUG_STATE_SETUP; + + return rc; +} + +static int pcm_plug_ioctl(void *data, unsigned int cmd, ...) +{ + struct pcm_plug_data *plug_data = data; + struct pcm_plugin *plugin = plug_data->plugin; + int ret; + va_list ap; + void *arg; + + va_start(ap, cmd); + arg = va_arg(ap, void *); + va_end(ap); + + switch (cmd) { + case SNDRV_PCM_IOCTL_INFO: + ret = pcm_plug_info(plug_data, arg); + break; + case SNDRV_PCM_IOCTL_TTSTAMP: + ret = pcm_plug_ttstamp(plug_data, arg); + break; + case SNDRV_PCM_IOCTL_HW_REFINE: + ret = pcm_plug_hrefine(plug_data, arg); + break; + case SNDRV_PCM_IOCTL_HW_PARAMS: + ret = pcm_plug_hparams(plug_data, arg); + break; + case SNDRV_PCM_IOCTL_SW_PARAMS: + ret = pcm_plug_sparams(plug_data, arg); + break; + case SNDRV_PCM_IOCTL_SYNC_PTR: + ret = pcm_plug_sync_ptr(plug_data, arg); + break; + case SNDRV_PCM_IOCTL_PREPARE: + ret = pcm_plug_prepare(plug_data); + break; + case SNDRV_PCM_IOCTL_START: + ret = pcm_plug_start(plug_data); + break; + case SNDRV_PCM_IOCTL_DROP: + ret = pcm_plug_drop(plug_data); + break; + case SNDRV_PCM_IOCTL_WRITEI_FRAMES: + ret = pcm_plug_writei_frames(plug_data, arg); + break; + case SNDRV_PCM_IOCTL_READI_FRAMES: + ret = pcm_plug_readi_frames(plug_data, arg); + break; + default: + ret = plug_data->ops->ioctl(plugin, cmd, arg); + break; + } + + return ret; +} + +static int pcm_plug_open(unsigned int card, unsigned int device, + unsigned int flags, void **data, struct snd_node *pcm_node) +{ + struct pcm_plug_data *plug_data; + void *dl_hdl; + int rc = 0; + char *so_name; + + plug_data = calloc(1, sizeof(*plug_data)); + if (!plug_data) { + return -ENOMEM; + } + + rc = snd_utils_get_str(pcm_node, "so-name", &so_name); + if (rc) { + fprintf(stderr, "%s: failed to get plugin lib name\n", __func__); + goto err_get_lib; + } + + dl_hdl = dlopen(so_name, RTLD_NOW); + if (!dl_hdl) { + fprintf(stderr, "%s: unable to open %s\n", __func__, so_name); + goto err_dl_open; + } else { + fprintf(stderr, "%s: dlopen successful for %s\n", __func__, so_name); + } + + dlerror(); + + plug_data->ops = dlsym(dl_hdl, "pcm_plugin_ops"); + if (!plug_data->ops) { + fprintf(stderr, "%s: dlsym to open fn failed, err = '%s'\n", + __func__, dlerror()); + goto err_dlsym; + } + + rc = plug_data->ops->open(&plug_data->plugin, card, device, flags); + if (rc) { + fprintf(stderr, "%s: failed to open plugin\n", __func__); + goto err_open; + } + + plug_data->dl_hdl = dl_hdl; + plug_data->card = card; + plug_data->device = device; + plug_data->dev_node = pcm_node; + plug_data->flags = flags; + + *data = plug_data; + + plug_data->plugin->state = PCM_PLUG_STATE_OPEN; + + return 0; + +err_open: +err_dlsym: + dlclose(dl_hdl); +err_get_lib: +err_dl_open: + free(plug_data); + + return rc; +} + +const struct pcm_ops plug_ops = { + .open = pcm_plug_open, + .close = pcm_plug_close, + .ioctl = pcm_plug_ioctl, +}; diff --git a/src/snd_card_plugin.c b/src/snd_card_plugin.c new file mode 100644 index 0000000..17912a2 --- /dev/null +++ b/src/snd_card_plugin.c @@ -0,0 +1,149 @@ +/* snd_card_plugin.c +** Copyright (c) 2019, The Linux Foundation. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above +** copyright notice, this list of conditions and the following +** disclaimer in the documentation and/or other materials provided +** with the distribution. +** * Neither the name of The Linux Foundation nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**/ + +#include "snd_card_plugin.h" + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#define SND_DLSYM(h, p, s, err) \ +do { \ + err = 0; \ + p = dlsym(h, s); \ + if (!p) \ + err = -ENODEV; \ +} while(0) + +int snd_utils_get_int(struct snd_node *node, const char *prop, int *val) +{ + if (!node || !node->card_node || !node->dev_node) + return SND_NODE_TYPE_HW; + + return node->ops->get_int(node->dev_node, prop, val); +} + +int snd_utils_get_str(struct snd_node *node, const char *prop, char **val) +{ + if (!node || !node->card_node || !node->dev_node) + return SND_NODE_TYPE_HW; + + return node->ops->get_str(node->dev_node, prop, val); +} + +void snd_utils_close_dev_node(struct snd_node *node) +{ + if (!node) + return; + + if (node->card_node) + node->ops->close_card(node->card_node); + + if (node->dl_hdl) + dlclose(node->dl_hdl); + + free(node); +} + +enum snd_node_type snd_utils_get_node_type(struct snd_node *node) +{ + int val = SND_NODE_TYPE_HW; + + if (!node || !node->card_node || !node->dev_node) + return SND_NODE_TYPE_HW; + + node->ops->get_int(node->dev_node, "type", &val); + + return val; +}; + +static int snd_utils_resolve_symbols(struct snd_node *node) +{ + void *dl = node->dl_hdl; + int err; + SND_DLSYM(dl, node->ops, "snd_card_ops", err); + return err; +} + +static struct snd_node *snd_utils_open_dev_node(unsigned int card, + unsigned int device, + int dev_type) +{ + struct snd_node *node; + int rc = 0; + + node = calloc(1, sizeof(*node)); + if (!node) + return NULL; + + node->dl_hdl = dlopen("libsndcardparser.so", RTLD_NOW); + if (!node->dl_hdl) { + goto err_dl_open; + } + + rc = snd_utils_resolve_symbols(node); + if (rc < 0) + goto err_resolve_symbols; + + node->card_node = node->ops->open_card(card); + if (!node->card_node) + goto err_resolve_symbols; + + if (dev_type == NODE_PCM) { + node->dev_node = node->ops->get_pcm(node->card_node, device); + } else { + node->dev_node = node->ops->get_mixer(node->card_node); + } + + if (!node->dev_node) + goto err_get_node; + + return node; + +err_get_node: + node->ops->close_card(node->card_node); + +err_resolve_symbols: + dlclose(node->dl_hdl); + +err_dl_open: + free(node); + return NULL; +} + +struct snd_node* snd_utils_open_pcm(unsigned int card, + unsigned int device) +{ + return snd_utils_open_dev_node(card, device, NODE_PCM); +} + +struct snd_node* snd_utils_open_mixer(unsigned int card) +{ + return snd_utils_open_dev_node(card, 0, NODE_MIXER); +} diff --git a/src/snd_card_plugin.h b/src/snd_card_plugin.h new file mode 100644 index 0000000..b80695e --- /dev/null +++ b/src/snd_card_plugin.h @@ -0,0 +1,74 @@ +/* snd_utils.h +** Copyright (c) 2019, The Linux Foundation. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above +** copyright notice, this list of conditions and the following +** disclaimer in the documentation and/or other materials provided +** with the distribution. +** * Neither the name of The Linux Foundation nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**/ + +#ifndef TINYALSA_SRC_SND_CARD_UTILS_H +#define TINYALSA_SRC_SND_CARD_UTILS_H + +#include <tinyalsa/plugin.h> + +#include <dlfcn.h> + +/** Encapsulates the pcm device definition from + * the sound card definition configuration file. + */ +struct snd_node { + /** Pointer the card definition */ + void *card_node; + /** Pointer to device definition, either PCM or MIXER device */ + void *dev_node; + /** Pointer to the sound card parser library */ + void *dl_hdl; + /** A pointer to the operations structure. */ + const struct snd_node_ops* ops; +}; + +enum snd_node_type { + SND_NODE_TYPE_HW = 0, + SND_NODE_TYPE_PLUGIN, + SND_NODE_TYPE_INVALID, +}; + +enum { + NODE_PCM, + NODE_MIXER +}; + +struct snd_node *snd_utils_open_pcm(unsigned int card, unsigned int device); + +struct snd_node *snd_utils_open_mixer(unsigned int card); + +void snd_utils_close_dev_node(struct snd_node *node); + +enum snd_node_type snd_utils_get_node_type(struct snd_node *node); + +int snd_utils_get_int(struct snd_node *node, const char *prop, int *val); + +int snd_utils_get_str(struct snd_node *node, const char *prop, char **val); + +#endif /* end of TINYALSA_SRC_SND_CARD_UTILS_H */ diff --git a/utils/Makefile b/utils/Makefile index 38cfc38..de34f94 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -19,19 +19,19 @@ VPATH = ../src:../include/tinyalsa .PHONY: all all: -ltinyalsa tinyplay tinycap tinymix tinypcminfo -tinyplay: tinyplay.o libtinyalsa.a +tinyplay: tinyplay.o libtinyalsa.a -ldl tinyplay.o: tinyplay.c pcm.h mixer.h asoundlib.h -tinycap: tinycap.o libtinyalsa.a +tinycap: tinycap.o libtinyalsa.a -ldl tinycap.o: tinycap.c pcm.h mixer.h asoundlib.h -tinymix: tinymix.o libtinyalsa.a +tinymix: tinymix.o libtinyalsa.a -ldl tinymix.o: tinymix.c pcm.h mixer.h asoundlib.h -tinypcminfo: tinypcminfo.o libtinyalsa.a +tinypcminfo: tinypcminfo.o libtinyalsa.a -ldl tinypcminfo.o: tinypcminfo.c pcm.h mixer.h asoundlib.h |