aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp7
-rw-r--r--CMakeLists.txt18
-rw-r--r--examples/Makefile4
-rw-r--r--include/tinyalsa/plugin.h261
-rw-r--r--meson.build10
-rw-r--r--src/Makefile12
-rw-r--r--src/mixer.c392
-rw-r--r--src/mixer_hw.c112
-rw-r--r--src/mixer_io.h51
-rw-r--r--src/mixer_plugin.c480
-rw-r--r--src/pcm.c121
-rw-r--r--src/pcm_hw.c118
-rw-r--r--src/pcm_io.h47
-rw-r--r--src/pcm_plugin.c718
-rw-r--r--src/snd_card_plugin.c149
-rw-r--r--src/snd_card_plugin.h74
-rw-r--r--utils/Makefile8
17 files changed, 2458 insertions, 124 deletions
diff --git a/Android.bp b/Android.bp
index 0d4122d..f316df9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -4,7 +4,12 @@ cc_library {
vendor_available: true,
srcs: [
"src/mixer.c",
+ "src/mixer_hw.c",
+ "src/mixer_plugin.c",
"src/pcm.c",
+ "src/pcm_hw.c",
+ "src/pcm_plugin.c",
+ "src/snd_card_plugin.c",
],
cflags: ["-Werror", "-Wno-macro-redefined"],
export_include_dirs: ["include"],
@@ -15,6 +20,8 @@ cc_library {
enabled: false,
},
},
+
+ system_shared_libs: ["libc", "libdl"],
}
cc_binary {
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1cc4a85..ba1aea3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,24 +2,37 @@ 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/mixer.c")
+ "src/pcm_hw.c"
+ "src/pcm_plugin.c"
+ "src/snd_card_plugin.c"
+ "src/mixer.c"
+ "src/mixer_hw.c"
+ "src/mixer_plugin.c")
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 +42,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..fed50be
--- /dev/null
+++ b/include/tinyalsa/plugin.h
@@ -0,0 +1,261 @@
+/* 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>
+
+/* static initializers */
+
+#define SND_VALUE_ENUM(etexts, eitems) \
+ {.texts = etexts, .items = eitems}
+
+#define SND_VALUE_BYTES(csize) \
+ {.size = csize }
+
+#define SND_VALUE_INTEGER(icount, imin, imax, istep) \
+ {.count = icount, .min = imin, .max = imax, .step = istep }
+
+#define SND_VALUE_TLV_BYTES(csize, cget, cput) \
+ {.size = csize, .get = cget, .put = cput }
+
+/* pointer based initializers */
+#define INIT_SND_CONTROL_INTEGER(c, cname, cget, cput, cint, pval, pdata) \
+ { \
+ c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \
+ c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; \
+ c->type = SNDRV_CTL_ELEM_TYPE_INTEGER; \
+ c->name = cname; c->value = &cint; c->get = cget; c->put = cput; \
+ c->private_value = pval; c->private_data = pdata; \
+ }
+
+#define INIT_SND_CONTROL_BYTES(c, cname, cget, cput, cint, pval, pdata) \
+ { \
+ c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \
+ c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; \
+ c->type = SNDRV_CTL_ELEM_TYPE_BYTES; \
+ c->name = cname; c->value = &cint; c->get = cget; c->put = cput; \
+ c->private_value = pval; c->private_data = pdata; \
+ }
+
+#define INIT_SND_CONTROL_ENUM(c, cname, cget, cput, cenum, pval, pdata) \
+ { \
+ c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \
+ c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; \
+ c->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; \
+ c->name = cname; c->value = cenum; c->get = cget; c->put = cput; \
+ c->private_value = pval; c->private_data = pdata; \
+ }
+
+#define INIT_SND_CONTROL_TLV_BYTES(c, cname, cbytes, priv_val, priv_data) \
+ { \
+ c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \
+ c->access = SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE; \
+ c->type = SNDRV_CTL_ELEM_TYPE_BYTES; \
+ c->name = cname; c->value = &cbytes; \
+ c->private_value = priv_val; c->private_data = priv_data; \
+ }
+
+struct mixer_plugin;
+struct pcm_plugin;
+struct snd_node;
+
+/** Operations that are required to be registered by the plugin.
+ * @ingroup libtinyalsa-pcm
+ */
+struct pcm_plugin_ops {
+ /** open the pcm plugin */
+ int (*open) (struct pcm_plugin **plugin, unsigned int card,
+ unsigned int device, unsigned int flags);
+ /** close the pcm plugin */
+ int (*close) (struct pcm_plugin *plugin);
+ /** Set the PCM hardware parameters to the plugin */
+ int (*hw_params) (struct pcm_plugin *plugin,
+ struct snd_pcm_hw_params *params);
+ /** Set the PCM software parameters to the plugin */
+ int (*sw_params) (struct pcm_plugin *plugin,
+ struct snd_pcm_sw_params *params);
+ /** Synchronize the pointer */
+ int (*sync_ptr) (struct pcm_plugin *plugin,
+ struct snd_pcm_sync_ptr *sync_ptr);
+ /** Write frames to plugin to be rendered to output */
+ int (*writei_frames) (struct pcm_plugin *plugin,
+ struct snd_xferi *x);
+ /** Read frames from plugin captured from input */
+ int (*readi_frames) (struct pcm_plugin *plugin,
+ struct snd_xferi *x);
+ /** Obtain the timestamp for the PCM */
+ int (*ttstamp) (struct pcm_plugin *plugin,
+ int *tstamp);
+ /** Prepare the plugin for data transfer */
+ int (*prepare) (struct pcm_plugin *plugin);
+ /** Start data transfer from/to the plugin */
+ int (*start) (struct pcm_plugin *plugin);
+ /** Drop pcm frames */
+ int (*drop) (struct pcm_plugin *plugin);
+ /** Any custom or alsa specific ioctl implementation */
+ int (*ioctl) (struct pcm_plugin *plugin,
+ int cmd, void *arg);
+};
+
+/** Minimum and maximum values for hardware parameter constraints.
+ * @ingroup libtinyalsa-pcm
+ */
+struct pcm_plugin_min_max {
+ /** Minimum value for the hardware parameter */
+ unsigned int min;
+ /** Maximum value for the hardware parameter */
+ unsigned int max;
+};
+
+/** Encapsulate the hardware parameter constraints
+ * @ingroup libtinyalsa-pcm
+ */
+struct pcm_plugin_hw_constraints {
+ /** Value for SNDRV_PCM_HW_PARAM_ACCESS param */
+ uint64_t access;
+ /** Value for SNDRV_PCM_HW_PARAM_FORMAT param.
+ * As of this implementation ALSA supports 52 formats */
+ uint64_t format;
+ /** Value for SNDRV_PCM_HW_PARAM_SAMPLE_BITS param */
+ struct pcm_plugin_min_max bit_width;
+ /** Value for SNDRV_PCM_HW_PARAM_CHANNELS param */
+ struct pcm_plugin_min_max channels;
+ /** Value for SNDRV_PCM_HW_PARAM_RATE param */
+ struct pcm_plugin_min_max rate;
+ /** Value for SNDRV_PCM_HW_PARAM_PERIODS param */
+ struct pcm_plugin_min_max periods;
+ /** Value for SNDRV_PCM_HW_PARAM_PERIOD_BYTES param */
+ struct pcm_plugin_min_max period_bytes;
+};
+
+struct pcm_plugin {
+ /** Card number for the pcm device */
+ unsigned int card;
+ /** device number for the pcm device */
+ unsigned int device;
+ /** pointer to the contraints registered by the plugin */
+ struct pcm_plugin_hw_constraints *constraints;
+ /** Indicates read/write mode, etc.. */
+ int mode;
+ /* Pointer to hold the plugin's private data */
+ void *priv;
+ /* Tracks the plugin state */
+ unsigned int state;
+};
+
+typedef void (*mixer_event_callback)(struct mixer_plugin *);
+
+struct mixer_plugin_ops {
+ int (*open) (struct mixer_plugin **plugin, unsigned int card);
+ void (*close) (struct mixer_plugin **plugin);
+ int (*subscribe_events) (struct mixer_plugin *plugin,
+ mixer_event_callback event_cb);
+ ssize_t (*read_event) (struct mixer_plugin *plugin,
+ struct snd_ctl_event *ev, size_t size);
+};
+
+struct snd_control {
+ snd_ctl_elem_iface_t iface;
+ unsigned int access;
+ const char *name;
+ snd_ctl_elem_type_t type;
+ void *value;
+ int (*get) (struct mixer_plugin *plugin,
+ struct snd_control *control,
+ struct snd_ctl_elem_value *ev);
+ int (*put) (struct mixer_plugin *plugin,
+ struct snd_control *control,
+ struct snd_ctl_elem_value *ev);
+ uint32_t private_value;
+ void *private_data;
+};
+
+struct mixer_plugin {
+ unsigned int card;
+ void *priv;
+
+ int eventfd;
+ int subscribed;
+ int event_cnt;
+
+ struct snd_control *controls;
+ unsigned int num_controls;
+};
+
+struct snd_value_enum {
+ unsigned int items;
+ char **texts;
+};
+
+struct snd_value_bytes {
+ unsigned int size;
+};
+
+struct snd_value_tlv_bytes {
+ unsigned int size;
+ int (*get) (struct mixer_plugin *plugin,
+ struct snd_control *control,
+ struct snd_ctl_tlv *tlv);
+ int (*put) (struct mixer_plugin *plugin,
+ struct snd_control *control,
+ struct snd_ctl_tlv *tlv);
+};
+
+struct snd_value_int {
+ unsigned int count;
+ int min;
+ int max;
+ int step;
+};
+
+/** Operations defined by the plugin.
+ * */
+struct snd_node_ops {
+ /** Function pointer to get card definition */
+ void* (*open_card)(unsigned int card);
+ /** Function pointer to release card definition */
+ void (*close_card)(void *card);
+ /** Get interger type properties from device definition */
+ int (*get_int)(void *node, const char *prop, int *val);
+ /** Get string type properties from device definition */
+ int (*get_str)(void *node, const char *prop, char **val);
+ /** Function pointer to get mixer definition */
+ void* (*get_mixer)(void *card);
+ /** Function pointer to get PCM definition */
+ void* (*get_pcm)(void *card, unsigned int id);
+ /** Reserved for other nodes such as compress */
+ void* reserved[4];
+};
+
+#endif /* end of TINYALSA_PLUGIN_H */
diff --git a/meson.build b/meson.build
index abbca8e..213f8c8 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', 'src/mixer_hw.c', 'src/mixer_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..79628b5 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 mixer_plugin.o mixer_hw.o
LIBVERSION_MAJOR = $(TINYALSA_VERSION_MAJOR)
LIBVERSION = $(TINYALSA_VERSION)
@@ -25,10 +25,20 @@ 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
+
+mixer_plugin.o: mixer_plugin.c mixer_io.h
+
+mixer_hw.o: mixer_hw.c mixer_io.h
+
libtinyalsa.a: $(OBJECTS)
$(AR) $(ARFLAGS) $@ $^
diff --git a/src/mixer.c b/src/mixer.c
index e496e18..1c9e63b 100644
--- a/src/mixer.c
+++ b/src/mixer.c
@@ -29,6 +29,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
+#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
@@ -57,6 +58,9 @@
#include <sound/asound.h>
#include <tinyalsa/mixer.h>
+#include <tinyalsa/plugin.h>
+
+#include "mixer_io.h"
/** A mixer control.
* @ingroup libtinyalsa-mixer
@@ -68,6 +72,21 @@ struct mixer_ctl {
struct snd_ctl_elem_info info;
/** A list of string representations of enumerated values (only valid for enumerated controls) */
char **ename;
+ /** Pointer to the group that the control belongs to */
+ struct mixer_ctl_group *grp;
+};
+
+struct mixer_ctl_group {
+ /** A continuous array of mixer controls */
+ struct mixer_ctl *ctl;
+ /** The number of mixer controls */
+ unsigned int count;
+ /** The number of events associated with this group */
+ unsigned int event_cnt;
+ /** The operations corresponding to this group */
+ const struct mixer_ops *ops;
+ /** Private data for storing group specific data */
+ void *data;
};
/** A mixer handle.
@@ -78,10 +97,15 @@ struct mixer {
int fd;
/** Card information */
struct snd_ctl_card_info card_info;
- /** A continuous array of mixer controls */
- struct mixer_ctl *ctl;
- /** The number of mixer controls */
- unsigned int count;
+ /* Hardware (kernel interface) mixer control group */
+ struct mixer_ctl_group *h_grp;
+ /* Virtual (Plugin interface) mixer control group */
+ struct mixer_ctl_group *v_grp;
+ /* Total count of mixer controls from both groups */
+ unsigned int total_count;
+ /* Flag to track if card information is already retrieved */
+ bool is_card_info_retrieved;
+
};
static void mixer_cleanup_control(struct mixer_ctl *ctl)
@@ -96,25 +120,40 @@ static void mixer_cleanup_control(struct mixer_ctl *ctl)
}
}
+static void mixer_grp_close(struct mixer *mixer, struct mixer_ctl_group *grp)
+{
+ unsigned int n;
+
+ if (!grp)
+ return;
+
+ if (grp->ctl) {
+ for (n = 0; n < grp->count; n++)
+ mixer_cleanup_control(&grp->ctl[n]);
+ free(grp->ctl);
+ }
+
+ mixer->is_card_info_retrieved = false;
+}
+
/** Closes a mixer returned by @ref mixer_open.
* @param mixer A mixer handle.
* @ingroup libtinyalsa-mixer
*/
void mixer_close(struct mixer *mixer)
{
- unsigned int n;
-
if (!mixer)
return;
- if (mixer->fd >= 0)
- close(mixer->fd);
+ if (mixer->fd >= 0 && mixer->h_grp)
+ mixer->h_grp->ops->close(mixer->h_grp->data);
+ mixer_grp_close(mixer, mixer->h_grp);
- if (mixer->ctl) {
- for (n = 0; n < mixer->count; n++)
- mixer_cleanup_control(&mixer->ctl[n]);
- free(mixer->ctl);
- }
+#ifdef TINYALSA_USES_PLUGINS
+ if (mixer->v_grp)
+ mixer->v_grp->ops->close(mixer->v_grp->data);
+ mixer_grp_close(mixer, mixer->v_grp);
+#endif
free(mixer);
@@ -133,18 +172,17 @@ static void *mixer_realloc_z(void *ptr, size_t curnum, size_t newnum, size_t siz
return newp;
}
-static int add_controls(struct mixer *mixer)
+static int add_controls(struct mixer *mixer, struct mixer_ctl_group *grp)
{
struct snd_ctl_elem_list elist;
struct snd_ctl_elem_id *eid = NULL;
struct mixer_ctl *ctl;
- int fd = mixer->fd;
- const unsigned int old_count = mixer->count;
+ const unsigned int old_count = grp->count;
unsigned int new_count;
unsigned int n;
memset(&elist, 0, sizeof(elist));
- if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
goto fail;
if (old_count == elist.count)
@@ -153,12 +191,12 @@ static int add_controls(struct mixer *mixer)
if (old_count > elist.count)
return -1; /* driver has removed controls - this is bad */
- ctl = mixer_realloc_z(mixer->ctl, old_count, elist.count,
+ ctl = mixer_realloc_z(grp->ctl, old_count, elist.count,
sizeof(struct mixer_ctl));
if (!ctl)
goto fail;
- mixer->ctl = ctl;
+ grp->ctl = ctl;
/* ALSA drivers are not supposed to remove or re-order controls that
* have already been created so we know that any new controls must
@@ -174,18 +212,21 @@ static int add_controls(struct mixer *mixer)
elist.pids = eid;
- if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
goto fail;
for (n = old_count; n < new_count; n++) {
- struct snd_ctl_elem_info *ei = &mixer->ctl[n].info;
+ struct snd_ctl_elem_info *ei = &grp->ctl[n].info;
ei->id.numid = eid[n - old_count].numid;
- if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0)
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0)
goto fail_extend;
ctl[n].mixer = mixer;
+ ctl[n].grp = grp;
}
- mixer->count = new_count;
+ grp->count = new_count;
+ mixer->total_count += (new_count - old_count);
+
free(eid);
return 0;
@@ -196,13 +237,69 @@ fail_extend:
*/
mixer_cleanup_control(&ctl[n]);
- mixer->count = n; /* keep controls we successfully added */
+ grp->count = n; /* keep controls we successfully added */
+ mixer->total_count += (n - old_count);
/* fall through... */
fail:
free(eid);
return -1;
}
+static int mixer_grp_open(struct mixer *mixer, unsigned int card, bool is_hw)
+{
+ struct mixer_ctl_group *grp = NULL;
+ const struct mixer_ops *ops = NULL;
+ void *data = NULL;
+ int fd, ret;
+
+ grp = calloc(1, sizeof(*grp));
+ if (!grp)
+ return -ENOMEM;
+
+ if (is_hw) {
+ mixer->fd = -1;
+ fd = mixer_hw_open(card, &data, &ops);
+ if (fd < 0) {
+ ret = fd;
+ goto err_open;
+ }
+ mixer->fd = fd;
+ mixer->h_grp = grp;
+ }
+#ifdef TINYALSA_USES_PLUGINS
+ else {
+ ret = mixer_plugin_open(card, &data, &ops);
+ if (ret < 0)
+ goto err_open;
+ mixer->v_grp = grp;
+ }
+#endif
+ grp->ops = ops;
+ grp->data = data;
+
+ if (!mixer->is_card_info_retrieved) {
+ ret = grp->ops->ioctl(data, SNDRV_CTL_IOCTL_CARD_INFO,
+ &mixer->card_info);
+ if (ret < 0)
+ goto err_card_info;
+ mixer->is_card_info_retrieved = true;
+ }
+
+ ret = add_controls(mixer, grp);
+ if (ret < 0)
+ goto err_card_info;
+
+ return 0;
+
+err_card_info:
+ grp->ops->close(grp->data);
+
+err_open:
+ free(grp);
+ return ret;
+
+}
+
/** Opens a mixer for a given card.
* @param card The card to open the mixer for.
* @returns An initialized mixer handle.
@@ -211,24 +308,20 @@ fail:
struct mixer *mixer_open(unsigned int card)
{
struct mixer *mixer = NULL;
- int fd;
- char fn[256];
-
- snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);
- fd = open(fn, O_RDWR);
- if (fd < 0)
- return 0;
+ int h_status, v_status = -1;
mixer = calloc(1, sizeof(*mixer));
if (!mixer)
goto fail;
- if (ioctl(fd, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info) < 0)
- goto fail;
+ h_status = mixer_grp_open(mixer, card, true);
- mixer->fd = fd;
+#ifdef TINYALSA_USES_PLUGINS
+ v_status = mixer_grp_open(mixer, card, false);
+#endif
- if (add_controls(mixer) != 0)
+ /* both hw and virtual should fail for mixer_open to fail */
+ if (h_status < 0 && v_status < 0)
goto fail;
return mixer;
@@ -236,8 +329,7 @@ struct mixer *mixer_open(unsigned int card)
fail:
if (mixer)
mixer_close(mixer);
- else if (fd >= 0)
- close(fd);
+
return NULL;
}
@@ -260,10 +352,27 @@ fail:
*/
int mixer_add_new_ctls(struct mixer *mixer)
{
+ int rc1 = 0, rc2 = 0;
+
if (!mixer)
return 0;
- return add_controls(mixer);
+ /* add the h_grp controls */
+ if (mixer->h_grp)
+ rc1 = add_controls(mixer, mixer->h_grp);
+
+#ifdef TINYALSA_USES_PLUGINS
+ /* add the v_grp controls */
+ if (mixer->v_grp)
+ rc2 = add_controls(mixer, mixer->v_grp);
+#endif
+
+ if (rc1 < 0)
+ return rc1;
+ if (rc2 < 0)
+ return rc2;
+
+ return 0;
}
/** Gets the name of the mixer's card.
@@ -286,7 +395,7 @@ unsigned int mixer_get_num_ctls(const struct mixer *mixer)
if (!mixer)
return 0;
- return mixer->count;
+ return mixer->total_count;
}
/** Gets the number of mixer controls, that go by a specified name, for a given mixer.
@@ -297,6 +406,7 @@ unsigned int mixer_get_num_ctls(const struct mixer *mixer)
*/
unsigned int mixer_get_num_ctls_by_name(const struct mixer *mixer, const char *name)
{
+ struct mixer_ctl_group *grp;
unsigned int n;
unsigned int count = 0;
struct mixer_ctl *ctl;
@@ -304,11 +414,24 @@ unsigned int mixer_get_num_ctls_by_name(const struct mixer *mixer, const char *n
if (!mixer)
return 0;
- ctl = mixer->ctl;
+ if (mixer->h_grp) {
+ grp = mixer->h_grp;
+ ctl = grp->ctl;
- for (n = 0; n < mixer->count; n++)
- if (!strcmp(name, (char*) ctl[n].info.id.name))
- count++;
+ for (n = 0; n < grp->count; n++)
+ if (!strcmp(name, (char*) ctl[n].info.id.name))
+ count++;
+ }
+#ifdef TINYALSA_USES_PLUGINS
+ if (mixer->v_grp) {
+ grp = mixer->v_grp;
+ ctl = grp->ctl;
+
+ for (n = 0; n < grp->count; n++)
+ if (!strcmp(name, (char*) ctl[n].info.id.name))
+ count++;
+ }
+#endif
return count;
}
@@ -322,9 +445,21 @@ unsigned int mixer_get_num_ctls_by_name(const struct mixer *mixer, const char *n
*/
int mixer_subscribe_events(struct mixer *mixer, int subscribe)
{
- if (ioctl(mixer->fd, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0) {
- return -1;
+ struct mixer_ctl_group *grp;
+
+ if (mixer->h_grp) {
+ grp = mixer->h_grp;
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0)
+ return -1;
+ }
+
+#ifdef TINYALSA_USES_PLUGINS
+ if (mixer->v_grp) {
+ grp = mixer->v_grp;
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0)
+ return -1;
}
+#endif
return 0;
}
@@ -338,25 +473,76 @@ int mixer_subscribe_events(struct mixer *mixer, int subscribe)
*/
int mixer_wait_event(struct mixer *mixer, int timeout)
{
- struct pollfd pfd;
+ struct pollfd *pfd;
+ struct mixer_ctl_group *grp;
+ int count = 0, num_fds = 0, i;
- pfd.fd = mixer->fd;
- pfd.events = POLLIN | POLLOUT | POLLERR | POLLNVAL;
+ if (mixer->fd >= 0)
+ num_fds++;
+
+#ifdef TINYALSA_USES_PLUGINS
+ if (mixer->v_grp)
+ num_fds++;
+#endif
+
+ pfd = (struct pollfd *)calloc(sizeof(struct pollfd), num_fds);
+ if (!pfd)
+ return -ENOMEM;
+
+ if (mixer->fd >= 0) {
+ pfd[count].fd = mixer->fd;
+ pfd[count].events = POLLIN | POLLOUT | POLLERR | POLLNVAL;
+ count++;
+ }
+
+#ifdef TINYALSA_USES_PLUGINS
+ if (mixer->v_grp) {
+ grp = mixer->v_grp;
+ if (!grp->ops->get_poll_fd(grp->data, pfd, count)) {
+ pfd[count].events = POLLIN | POLLERR | POLLNVAL;
+ count++;
+ }
+ }
+#endif
+
+ if (!count)
+ return 0;
for (;;) {
int err;
- err = poll(&pfd, 1, timeout);
+ err = poll(pfd, count, timeout);
if (err < 0)
return -errno;
if (!err)
return 0;
- if (pfd.revents & (POLLERR | POLLNVAL))
- return -EIO;
- if (pfd.revents & (POLLIN | POLLOUT))
- return 1;
+ for (i = 0; i < count; i++) {
+ if (pfd[i].revents & (POLLERR | POLLNVAL))
+ return -EIO;
+ if (pfd[i].revents & (POLLIN | POLLOUT)) {
+ if ((i == 0) && mixer->fd >= 0) {
+ grp = mixer->h_grp;
+ grp->event_cnt++;
+ }
+#ifdef TINYALSA_USES_PLUGINS
+ else {
+ grp = mixer->v_grp;
+ grp->event_cnt++;
+ }
+#endif
+ return 1;
+ }
+ }
}
}
+static unsigned int mixer_grp_get_count(struct mixer_ctl_group *grp)
+{
+ if (!grp)
+ return 0;
+
+ return grp->count;
+}
+
/** Gets a mixer control handle, by the mixer control's id.
* For non-const access, see @ref mixer_get_ctl
* @param mixer An initialized mixer handle.
@@ -366,8 +552,22 @@ int mixer_wait_event(struct mixer *mixer, int timeout)
*/
const struct mixer_ctl *mixer_get_ctl_const(const struct mixer *mixer, unsigned int id)
{
- if (mixer && (id < mixer->count))
- return mixer->ctl + id;
+ unsigned int h_count;
+
+ if (!mixer || (id >= mixer->total_count))
+ return NULL;
+
+ h_count = mixer_grp_get_count(mixer->h_grp);
+
+ if (id < h_count)
+ return mixer->h_grp->ctl + id;
+#ifdef TINYALSA_USES_PLUGINS
+ else {
+ unsigned int v_count = mixer_grp_get_count(mixer->v_grp);
+ if ((id - h_count) < v_count)
+ return mixer->v_grp->ctl + (id - h_count);
+ }
+#endif
return NULL;
}
@@ -381,9 +581,22 @@ const struct mixer_ctl *mixer_get_ctl_const(const struct mixer *mixer, unsigned
*/
struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id)
{
- if (mixer && (id < mixer->count))
- return mixer->ctl + id;
+ unsigned int h_count;
+
+ if (!mixer || (id >= mixer->total_count))
+ return NULL;
+
+ h_count = mixer_grp_get_count(mixer->h_grp);
+ if (id < h_count)
+ return mixer->h_grp->ctl + id;
+#ifdef TINYALSA_USES_PLUGINS
+ else {
+ unsigned int v_count = mixer_grp_get_count(mixer->v_grp);
+ if ((id - h_count) < v_count)
+ return mixer->v_grp->ctl + (id - h_count);
+ }
+#endif
return NULL;
}
@@ -410,19 +623,34 @@ struct mixer_ctl *mixer_get_ctl_by_name_and_index(struct mixer *mixer,
const char *name,
unsigned int index)
{
+ struct mixer_ctl_group *grp;
unsigned int n;
struct mixer_ctl *ctl;
if (!mixer)
return NULL;
- ctl = mixer->ctl;
+ if (mixer->h_grp) {
+ grp = mixer->h_grp;
+ ctl = grp->ctl;
- for (n = 0; n < mixer->count; n++)
- if (!strcmp(name, (char*) ctl[n].info.id.name))
- if (index-- == 0)
- return mixer->ctl + n;
+ for (n = 0; n < grp->count; n++)
+ if (!strcmp(name, (char*) ctl[n].info.id.name))
+ if (index-- == 0)
+ return ctl + n;
+ }
+
+#ifdef TINYALSA_USES_PLUGINS
+ if (mixer->v_grp) {
+ grp = mixer->v_grp;
+ ctl = grp->ctl;
+ for (n = 0; n < grp->count; n++)
+ if (!strcmp(name, (char*) ctl[n].info.id.name))
+ if (index-- == 0)
+ return ctl + n;
+ }
+#endif
return NULL;
}
@@ -433,7 +661,13 @@ struct mixer_ctl *mixer_get_ctl_by_name_and_index(struct mixer *mixer,
*/
void mixer_ctl_update(struct mixer_ctl *ctl)
{
- ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, ctl->info);
+ struct mixer_ctl_group *grp;
+
+ if (!ctl)
+ return;
+
+ grp = ctl->grp;
+ grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, &ctl->info);
}
/** Checks the control for TLV Read/Write access.
@@ -595,15 +829,17 @@ int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent)
*/
int mixer_ctl_get_value(const struct mixer_ctl *ctl, unsigned int id)
{
+ struct mixer_ctl_group *grp;
struct snd_ctl_elem_value ev;
int ret;
if (!ctl || (id >= ctl->info.count))
return -EINVAL;
+ grp = ctl->grp;
memset(&ev, 0, sizeof(ev));
ev.id.numid = ctl->info.id.numid;
- ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
if (ret < 0)
return ret;
@@ -641,6 +877,7 @@ int mixer_ctl_get_value(const struct mixer_ctl *ctl, unsigned int id)
*/
int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count)
{
+ struct mixer_ctl_group *grp;
struct snd_ctl_elem_value ev;
int ret = 0;
size_t size;
@@ -650,6 +887,7 @@ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count)
if (!ctl || !count || !array)
return -EINVAL;
+ grp = ctl->grp;
total_count = ctl->info.count;
if ((ctl->info.type == SNDRV_CTL_ELEM_TYPE_BYTES) &&
@@ -667,7 +905,7 @@ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count)
switch (ctl->info.type) {
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
case SNDRV_CTL_ELEM_TYPE_INTEGER:
- ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
if (ret < 0)
return ret;
size = sizeof(ev.value.integer.value[0]);
@@ -687,7 +925,7 @@ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count)
return -ENOMEM;
tlv->numid = ctl->info.id.numid;
tlv->length = count;
- ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_TLV_READ, tlv);
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_TLV_READ, tlv);
source = tlv->tlv;
memcpy(array, source, count);
@@ -696,7 +934,7 @@ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count)
return ret;
} else {
- ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
if (ret < 0)
return ret;
size = sizeof(ev.value.bytes.data[0]);
@@ -725,15 +963,17 @@ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count)
*/
int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)
{
+ struct mixer_ctl_group *grp;
struct snd_ctl_elem_value ev;
int ret;
if (!ctl || (id >= ctl->info.count))
return -EINVAL;
+ grp = ctl->grp;
memset(&ev, 0, sizeof(ev));
ev.id.numid = ctl->info.id.numid;
- ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
if (ret < 0)
return ret;
@@ -763,7 +1003,7 @@ int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)
return -EINVAL;
}
- return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
+ return grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
}
/** Sets the contents of a control's value array.
@@ -778,6 +1018,7 @@ int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)
*/
int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count)
{
+ struct mixer_ctl_group *grp;
struct snd_ctl_elem_value ev;
size_t size;
void *dest;
@@ -786,6 +1027,7 @@ int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count)
if ((!ctl) || !count || !array)
return -EINVAL;
+ grp = ctl->grp;
total_count = ctl->info.count;
if ((ctl->info.type == SNDRV_CTL_ELEM_TYPE_BYTES) &&
@@ -821,7 +1063,7 @@ int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count)
tlv->length = count;
memcpy(tlv->tlv, array, count);
- ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_TLV_WRITE, tlv);
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_TLV_WRITE, tlv);
free(tlv);
return ret;
@@ -837,7 +1079,7 @@ int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count)
memcpy(dest, array, size * count);
- return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
+ return grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
}
/** Gets the minimum value of an control.
@@ -887,6 +1129,7 @@ unsigned int mixer_ctl_get_num_enums(const struct mixer_ctl *ctl)
int mixer_ctl_fill_enum_string(struct mixer_ctl *ctl)
{
+ struct mixer_ctl_group *grp = ctl->grp;
struct snd_ctl_elem_info tmp;
unsigned int m;
char **enames;
@@ -902,7 +1145,7 @@ int mixer_ctl_fill_enum_string(struct mixer_ctl *ctl)
memset(&tmp, 0, sizeof(tmp));
tmp.id.numid = ctl->info.id.numid;
tmp.value.enumerated.item = m;
- if (ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0)
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0)
goto fail;
enames[m] = strdup(tmp.value.enumerated.name);
if (!enames[m])
@@ -949,6 +1192,7 @@ const char *mixer_ctl_get_enum_string(struct mixer_ctl *ctl,
*/
int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string)
{
+ struct mixer_ctl_group *grp;
unsigned int i, num_enums;
struct snd_ctl_elem_value ev;
int ret;
@@ -957,13 +1201,14 @@ int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string)
mixer_ctl_fill_enum_string(ctl) != 0)
return -EINVAL;
+ grp = ctl->grp;
num_enums = ctl->info.value.enumerated.items;
for (i = 0; i < num_enums; i++) {
if (!strcmp(string, ctl->ename[i])) {
memset(&ev, 0, sizeof(ev));
ev.value.enumerated.item[0] = i;
ev.id.numid = ctl->info.id.numid;
- ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
+ ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
if (ret < 0)
return ret;
return 0;
@@ -972,4 +1217,3 @@ int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string)
return -EINVAL;
}
-
diff --git a/src/mixer_hw.c b/src/mixer_hw.c
new file mode 100644
index 0000000..2e86dfa
--- /dev/null
+++ b/src/mixer_hw.c
@@ -0,0 +1,112 @@
+/* mixer_hw.c
+** Copyright (c) 2019, The Linux Foundation.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <poll.h>
+
+#include <sys/ioctl.h>
+
+#include <linux/ioctl.h>
+#include <sound/asound.h>
+
+#include "mixer_io.h"
+
+/** Store the hardware (kernel interface) mixer data */
+struct mixer_hw_data {
+ /* Card number for the mixer */
+ unsigned int card;
+ /* File descriptor of the mixer device node */
+ int fd;
+};
+
+static void mixer_hw_close(void *data)
+{
+ struct mixer_hw_data *hw_data = data;
+
+ if (!hw_data)
+ return;
+
+ if (hw_data->fd >= 0)
+ close(hw_data->fd);
+
+ hw_data->fd = -1;
+ free(hw_data);
+ hw_data = NULL;
+}
+
+static int mixer_hw_ioctl(void *data, unsigned int cmd, ...)
+{
+ struct mixer_hw_data *hw_data = data;
+ va_list ap;
+ void *arg;
+
+ va_start(ap, cmd);
+ arg = va_arg(ap, void *);
+ va_end(ap);
+
+ return ioctl(hw_data->fd, cmd, arg);
+}
+
+static const struct mixer_ops mixer_hw_ops = {
+ .close = mixer_hw_close,
+ .ioctl = mixer_hw_ioctl,
+};
+
+int mixer_hw_open(unsigned int card, void **data,
+ const struct mixer_ops **ops)
+{
+ struct mixer_hw_data *hw_data;
+ int fd;
+ char fn[256];
+
+ snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);
+ fd = open(fn, O_RDWR);
+ if (fd < 0)
+ return fd;
+
+ hw_data = calloc(1, sizeof(*hw_data));
+ if (!hw_data)
+ return -1;
+
+ hw_data->card = card;
+ hw_data->fd = fd;
+ *data = hw_data;
+ *ops = &mixer_hw_ops;
+
+ return fd;
+}
diff --git a/src/mixer_io.h b/src/mixer_io.h
new file mode 100644
index 0000000..bb3bc44
--- /dev/null
+++ b/src/mixer_io.h
@@ -0,0 +1,51 @@
+/* mixer_io.h
+** Copyright (c) 2019, The Linux Foundation.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+#ifndef TINYALSA_SRC_MIXER_H
+#define TINYALSA_SRC_MIXER_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sound/asound.h>
+
+struct mixer_ops;
+
+int mixer_hw_open(unsigned int card, void **data,
+ const struct mixer_ops **ops);
+int mixer_plugin_open(unsigned int card, void **data,
+ const struct mixer_ops **ops);
+
+struct mixer_ops {
+ void (*close) (void *data);
+ int (*get_poll_fd) (void *data, struct pollfd *pfd, int count);
+ ssize_t (*read_event) (void *data, struct snd_ctl_event *ev, size_t size);
+ int (*ioctl) (void *data, unsigned int cmd, ...);
+};
+
+#endif /* TINYALSA_SRC_MIXER_H */
diff --git a/src/mixer_plugin.c b/src/mixer_plugin.c
new file mode 100644
index 0000000..3437171
--- /dev/null
+++ b/src/mixer_plugin.c
@@ -0,0 +1,480 @@
+/* mixer_plugin.c
+** Copyright (c) 2019, The Linux Foundation.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+#include <tinyalsa/plugin.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <poll.h>
+#include <dlfcn.h>
+#include <string.h>
+#include <sys/eventfd.h>
+#include <sys/ioctl.h>
+
+#include <linux/ioctl.h>
+#include <sound/asound.h>
+
+#include "snd_card_plugin.h"
+#include "mixer_io.h"
+
+/** Encapulates the mixer plugin specific data */
+struct mixer_plug_data {
+ /** Card number associated with the plugin */
+ int card;
+ /** Device node for mixer */
+ void *mixer_node;
+ /** Pointer to the plugin's ops */
+ const struct mixer_plugin_ops *ops;
+ /** Pointer to plugin responsible to service the controls */
+ struct mixer_plugin *plugin;
+ /** Handle to the plugin library */
+ void *dl_hdl;
+};
+
+static int mixer_plug_get_elem_id(struct mixer_plug_data *plug_data,
+ struct snd_ctl_elem_id *id, unsigned int offset)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+
+ if (offset >= plugin->num_controls) {
+ fprintf(stderr, "%s: invalid offset %u\n",
+ __func__, offset);
+ return -EINVAL;
+ }
+
+ ctl = plugin->controls + offset;
+ id->numid = offset;
+ id->iface = ctl->iface;
+
+ strncpy((char *)id->name, (char *)ctl->name,
+ sizeof(id->name));
+
+ return 0;
+}
+
+static int mixer_plug_info_enum(struct snd_control *ctl,
+ struct snd_ctl_elem_info *einfo)
+{
+ struct snd_value_enum *val = ctl->value;
+
+ einfo->count = 1;
+ einfo->value.enumerated.items = val->items;
+
+ if (einfo->value.enumerated.item > val->items)
+ return -EINVAL;
+
+ strncpy(einfo->value.enumerated.name,
+ val->texts[einfo->value.enumerated.item],
+ sizeof(einfo->value.enumerated.name));
+
+ return 0;
+}
+
+static int mixer_plug_info_bytes(struct snd_control *ctl,
+ struct snd_ctl_elem_info *einfo)
+{
+ struct snd_value_bytes *val;
+ struct snd_value_tlv_bytes *val_tlv;
+
+ if (ctl->access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) {
+ val_tlv = ctl->value;
+ einfo->count = val_tlv->size;
+ } else {
+ val = ctl->value;
+ einfo->count = val->size;
+ }
+
+ return 0;
+}
+
+static int mixer_plug_info_integer(struct snd_control *ctl,
+ struct snd_ctl_elem_info *einfo)
+{
+ struct snd_value_int *val = ctl->value;
+
+ einfo->count = val->count;
+ einfo->value.integer.min = val->min;
+ einfo->value.integer.max = val->max;
+ einfo->value.integer.step = val->step;
+
+ return 0;
+}
+
+void mixer_plug_notifier_cb(struct mixer_plugin *plugin)
+{
+ eventfd_write(plugin->eventfd, 1);
+ plugin->event_cnt++;
+}
+
+/* In consume_event/read, do not call eventfd_read until all events are read from list.
+ This will make poll getting unblocked until all events are read */
+static ssize_t mixer_plug_read_event(void *data, struct snd_ctl_event *ev, size_t size)
+{
+ struct mixer_plug_data *plug_data = data;
+ struct mixer_plugin *plugin = plug_data->plugin;
+ eventfd_t evfd;
+ ssize_t result = 0;
+
+ result = plug_data->ops->read_event(plugin, ev, size);
+
+ if (result > 0) {
+ plugin->event_cnt -= result / sizeof(struct snd_ctl_event);
+ if (plugin->event_cnt == 0)
+ eventfd_read(plugin->eventfd, &evfd);
+ }
+
+ return result;
+}
+
+static int mixer_plug_subscribe_events(struct mixer_plug_data *plug_data,
+ int *subscribe)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ eventfd_t evfd;
+
+ if (*subscribe < 0 || *subscribe > 1) {
+ *subscribe = plugin->subscribed;
+ return -EINVAL;
+ }
+
+ if (*subscribe && !plugin->subscribed) {
+ plug_data->ops->subscribe_events(plugin, &mixer_plug_notifier_cb);
+ } else if (plugin->subscribed && !*subscribe) {
+ plug_data->ops->subscribe_events(plugin, NULL);
+
+ if (plugin->event_cnt)
+ eventfd_read(plugin->eventfd, &evfd);
+
+ plugin->event_cnt = 0;
+ }
+
+ plugin->subscribed = *subscribe;
+ return 0;
+}
+
+static int mixer_plug_get_poll_fd(void *data, struct pollfd *pfd, int count)
+{
+ struct mixer_plug_data *plug_data = data;
+ struct mixer_plugin *plugin = plug_data->plugin;
+
+ if (plugin->eventfd != -1) {
+ pfd[count].fd = plugin->eventfd;
+ return 0;
+ }
+ return -ENODEV;
+}
+
+static int mixer_plug_tlv_write(struct mixer_plug_data *plug_data,
+ struct snd_ctl_tlv *tlv)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+ struct snd_value_tlv_bytes *val_tlv;
+
+ ctl = plugin->controls + tlv->numid;
+ val_tlv = ctl->value;
+
+ return val_tlv->put(plugin, ctl, tlv);
+}
+
+static int mixer_plug_tlv_read(struct mixer_plug_data *plug_data,
+ struct snd_ctl_tlv *tlv)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+ struct snd_value_tlv_bytes *val_tlv;
+
+ ctl = plugin->controls + tlv->numid;
+ val_tlv = ctl->value;
+
+ return val_tlv->get(plugin, ctl, tlv);
+}
+
+static int mixer_plug_elem_write(struct mixer_plug_data *plug_data,
+ struct snd_ctl_elem_value *ev)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+ int ret;
+
+ ret = mixer_plug_get_elem_id(plug_data, &ev->id, ev->id.numid);
+ if (ret < 0)
+ return ret;
+
+ ctl = plugin->controls + ev->id.numid;
+
+ return ctl->put(plugin, ctl, ev);
+}
+
+static int mixer_plug_elem_read(struct mixer_plug_data *plug_data,
+ struct snd_ctl_elem_value *ev)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+ int ret;
+
+ ret = mixer_plug_get_elem_id(plug_data, &ev->id, ev->id.numid);
+ if (ret < 0)
+ return ret;
+
+ ctl = plugin->controls + ev->id.numid;
+
+ return ctl->get(plugin, ctl, ev);
+
+}
+
+static int mixer_plug_get_elem_info(struct mixer_plug_data *plug_data,
+ struct snd_ctl_elem_info *einfo)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ struct snd_control *ctl;
+ int ret;
+
+ ret = mixer_plug_get_elem_id(plug_data, &einfo->id,
+ einfo->id.numid);
+ if (ret < 0)
+ return ret;
+
+ ctl = plugin->controls + einfo->id.numid;
+ einfo->type = ctl->type;
+ einfo->access = ctl->access;
+
+ switch (einfo->type) {
+ case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+ ret = mixer_plug_info_enum(ctl, einfo);
+ if (ret < 0)
+ return ret;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_BYTES:
+ ret = mixer_plug_info_bytes(ctl, einfo);
+ if (ret < 0)
+ return ret;
+ break;
+ case SNDRV_CTL_ELEM_TYPE_INTEGER:
+ ret = mixer_plug_info_integer(ctl, einfo);
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ fprintf(stderr,"%s: unknown type %d\n",
+ __func__, einfo->type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mixer_plug_get_elem_list(struct mixer_plug_data *plug_data,
+ struct snd_ctl_elem_list *elist)
+{
+ struct mixer_plugin *plugin = plug_data->plugin;
+ unsigned int avail;
+ struct snd_ctl_elem_id *id;
+ int ret;
+
+ elist->count = plugin->num_controls;
+ elist->used = 0;
+ avail = elist->space;
+
+ while (avail > 0) {
+ id = elist->pids + elist->used;
+ ret = mixer_plug_get_elem_id(plug_data, id, elist->used);
+ if (ret < 0)
+ return ret;
+
+ avail--;
+ elist->used++;
+ }
+
+ return 0;
+}
+
+static int mixer_plug_get_card_info(struct mixer_plug_data *plug_data,
+ struct snd_ctl_card_info *card_info)
+{
+ /*TODO: Fill card_info here from snd-card-def */
+ memset(card_info, 0, sizeof(*card_info));
+ card_info->card = plug_data->card;
+ memcpy(card_info->id, "card_id", sizeof(card_info->id));
+ memcpy(card_info->driver, "mymixer-so-name", sizeof(card_info->driver));
+ memcpy(card_info->name, "card-name", sizeof(card_info->name));
+ memcpy(card_info->longname, "card-name", sizeof(card_info->longname));
+ memcpy(card_info->mixername, "mixer-name", sizeof(card_info->mixername));
+
+ return 0;
+}
+
+static void mixer_plug_close(void *data)
+{
+ struct mixer_plug_data *plug_data = data;
+ struct mixer_plugin *plugin = plug_data->plugin;
+ eventfd_t evfd;
+
+ if (plugin->event_cnt)
+ eventfd_read(plugin->eventfd, &evfd);
+
+ plug_data->ops->close(&plugin);
+ dlclose(plug_data->dl_hdl);
+
+ free(plug_data);
+ plug_data = NULL;
+}
+
+static int mixer_plug_ioctl(void *data, unsigned int cmd, ...)
+{
+ struct mixer_plug_data *plug_data = data;
+ int ret;
+ va_list ap;
+ void *arg;
+
+ va_start(ap, cmd);
+ arg = va_arg(ap, void *);
+ va_end(ap);
+
+ switch (cmd) {
+ case SNDRV_CTL_IOCTL_CARD_INFO:
+ ret = mixer_plug_get_card_info(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_ELEM_LIST:
+ ret = mixer_plug_get_elem_list(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_ELEM_INFO:
+ ret = mixer_plug_get_elem_info(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_ELEM_READ:
+ ret = mixer_plug_elem_read(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_ELEM_WRITE:
+ ret = mixer_plug_elem_write(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_TLV_READ:
+ ret = mixer_plug_tlv_read(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_TLV_WRITE:
+ ret = mixer_plug_tlv_write(plug_data, arg);
+ break;
+ case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
+ ret = mixer_plug_subscribe_events(plug_data, arg);
+ break;
+ default:
+ /* TODO: plugin should support ioctl */
+ ret = -EFAULT;
+ break;
+ }
+
+ return ret;
+}
+
+static const struct mixer_ops mixer_plug_ops = {
+ .close = mixer_plug_close,
+ .read_event = mixer_plug_read_event,
+ .get_poll_fd = mixer_plug_get_poll_fd,
+ .ioctl = mixer_plug_ioctl,
+};
+
+int mixer_plugin_open(unsigned int card, void **data,
+ const struct mixer_ops **ops)
+{
+ struct mixer_plug_data *plug_data;
+ struct mixer_plugin *plugin = NULL;
+ void *dl_hdl;
+ char *so_name;
+ int ret;
+
+ plug_data = calloc(1, sizeof(*plug_data));
+ if (!plug_data)
+ return -ENOMEM;
+
+ plug_data->mixer_node = snd_utils_open_mixer(card);
+ if (!plug_data->mixer_node) {
+ /* Do not print error here.
+ * It is valid for card to not have virtual mixer node
+ */
+ goto err_get_mixer_node;
+ }
+
+ ret = snd_utils_get_str(plug_data->mixer_node, "so-name",
+ &so_name);
+ if(ret) {
+ fprintf(stderr, "%s: mixer so-name not found for card %u\n",
+ __func__, card);
+ goto err_get_lib_name;
+
+ }
+
+ dl_hdl = dlopen(so_name, RTLD_NOW);
+ if (!dl_hdl) {
+ fprintf(stderr, "%s: unable to open %s\n",
+ __func__, so_name);
+ goto err_dlopen;
+ }
+
+ dlerror();
+ plug_data->ops = dlsym(dl_hdl, "mixer_plugin_ops");
+ if (!plug_data->ops) {
+ fprintf(stderr, "%s: dlsym open fn failed: %s\n",
+ __func__, dlerror());
+ goto err_ops;
+ }
+
+ ret = plug_data->ops->open(&plugin, card);
+ if (ret) {
+ fprintf(stderr, "%s: failed to open plugin, err: %d\n",
+ __func__, ret);
+ goto err_ops;
+ }
+
+ plug_data->plugin = plugin;
+ plug_data->card = card;
+ plug_data->dl_hdl = dl_hdl;
+ plugin->eventfd = eventfd(0, 0);
+
+ *data = plug_data;
+ *ops = &mixer_plug_ops;
+
+ return 0;
+
+err_ops:
+ dlclose(dl_hdl);
+err_dlopen:
+err_get_lib_name:
+ snd_utils_close_dev_node(plug_data->mixer_node);
+err_get_mixer_node:
+ free(plug_data);
+ return -1;
+}
diff --git a/src/pcm.c b/src/pcm.c
index c763d4c..c28e098 100644
--- a/src/pcm.c
+++ b/src/pcm.c
@@ -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(&params, SNDRV_PCM_HW_PARAM_ACCESS,
SNDRV_PCM_ACCESS_RW_INTERLEAVED);
- if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {
+ if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {
int errno_copy = errno;
oops(pcm, -errno, "cannot set hw params");
return -errno_copy;
@@ -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 = &params->intervals[p - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL];
+
+ i->min = v->min;
+ i->max = v->max;
+
+ if (is_integer)
+ i->integer = 1;
+}
+
+static int pcm_plug_frames_to_bytes(unsigned int frames,
+ unsigned int frame_bits)
+{
+ return (frames * (frame_bits / 8));
+}
+
+static int pcm_plug_bytes_to_frames(unsigned int size,
+ unsigned int frame_bits)
+{
+ return (size * 8) / frame_bits;
+}
+
+static int pcm_plug_get_params(struct pcm_plugin *plugin,
+ struct snd_pcm_hw_params *params)
+{
+ struct pcm_plugin_min_max bw, ch, pb, periods;
+ struct pcm_plugin_min_max val;
+ struct pcm_plugin_min_max frame_bits, buffer_bytes;
+
+ /*
+ * populate the struct snd_pcm_hw_params structure
+ * using the hw_param constraints provided by plugin
+ * via the plugin->constraints
+ */
+
+ /* Set the mask params */
+ pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_ACCESS,
+ plugin->constraints->access);
+ pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_FORMAT,
+ plugin->constraints->format);
+ pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT,
+ SNDRV_PCM_SUBFORMAT_STD);
+
+ /* Set the standard interval params */
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ &plugin->constraints->bit_width, 1);
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &plugin->constraints->channels, 1);
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_RATE,
+ &plugin->constraints->rate, 1);
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ &plugin->constraints->period_bytes, 0);
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIODS,
+ &plugin->constraints->periods, 1);
+
+ /* set the calculated interval params */
+
+ bw.min = plugin->constraints->bit_width.min;
+ bw.max = plugin->constraints->bit_width.max;
+
+ ch.min = plugin->constraints->channels.min;
+ ch.max = plugin->constraints->channels.max;
+
+ pb.min = plugin->constraints->period_bytes.min;
+ pb.max = plugin->constraints->period_bytes.max;
+
+ periods.min = plugin->constraints->periods.min;
+ periods.max = plugin->constraints->periods.max;
+
+ /* Calculate and set frame bits */
+ frame_bits.min = bw.min * ch.min;
+ frame_bits.max = bw.max * ch.max;
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS,
+ &frame_bits, 1);
+
+
+ /* Calculate and set period_size in frames */
+ val.min = pcm_plug_bytes_to_frames(pb.min, frame_bits.min);
+ val.max = pcm_plug_bytes_to_frames(pb.max, frame_bits.min);
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ &val, 1);
+
+ /* Calculate and set buffer_bytes */
+ buffer_bytes.min = pb.min * periods.min;
+ buffer_bytes.max = pb.max * periods.max;
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ &buffer_bytes, 1);
+
+ /* Calculate and set buffer_size in frames */
+ val.min = pcm_plug_bytes_to_frames(buffer_bytes.min, frame_bits.min);
+ val.max = pcm_plug_bytes_to_frames(buffer_bytes.max, frame_bits.min);
+ pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+ &val, 1);
+ return 0;
+}
+
+static int pcm_plug_masks_refine(struct snd_pcm_hw_params *p,
+ struct snd_pcm_hw_params *c)
+{
+ struct snd_mask *req_mask;
+ struct snd_mask *con_mask;
+ unsigned int idx, i, masks;
+
+ masks = SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK;
+
+ for (idx = 0; idx <= masks; idx++) {
+
+ if (!(p->rmask & (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_MASK))))
+ continue;
+
+ req_mask = PCM_PARAM_GET_MASK(p, idx);
+ con_mask = PCM_PARAM_GET_MASK(c, idx);
+
+ /*
+ * set the changed mask if requested mask value is not the same as
+ * constrained mask value
+ */
+ if (memcmp(req_mask, con_mask, PCM_MASK_SIZE * sizeof(uint32_t)))
+ p->cmask |= 1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_MASK);
+
+ /* Actually change the requested mask to constrained mask */
+ for (i = 0; i < PCM_MASK_SIZE; i++)
+ req_mask->bits[i] &= con_mask->bits[i];
+ }
+
+ return 0;
+}
+
+static int pcm_plug_interval_refine(struct snd_pcm_hw_params *p,
+ struct snd_pcm_hw_params *c)
+{
+ struct snd_interval *ri;
+ struct snd_interval *ci;
+ unsigned int idx;
+ unsigned int intervals;
+ int changed = 0;
+
+ intervals = SNDRV_PCM_HW_PARAM_LAST_INTERVAL -
+ SNDRV_PCM_HW_PARAM_FIRST_INTERVAL;
+
+ for (idx = 0; idx <= intervals; idx++) {
+ ri = &p->intervals[idx];
+ ci = &c->intervals[idx];
+
+ if (!(p->rmask & (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_INTERVAL)) ))
+ continue;
+
+ if (ri->min < ci->min) {
+ ri->min = ci->min;
+ ri->openmin = ci->openmin;
+ changed = 1;
+ } else if (ri->min == ci->min && !ri->openmin && ci->openmin) {
+ ri->openmin = 1;
+ changed = 1;
+ }
+
+ if (ri->max > ci->max) {
+ ri->max = ci->max;
+ ri->openmax = ci->openmax;
+ changed = 1;
+ } else if (ri->max == ci->max && !ri->openmax && ci->openmax) {
+ ri->openmax = 1;
+ changed = 1;
+ };
+
+ if (!ri->integer && ci->integer) {
+ ri->integer = 1;
+ changed = 1;
+ }
+
+ if (ri->integer) {
+ if (ri->openmin) {
+ ri->min++;
+ ri->openmin = 0;
+ }
+ if (ri->openmax) {
+ ri->max--;
+ ri->openmax = 0;
+ }
+ } else if (!ri->openmin && !ri->openmax && ri->min == ri->max) {
+ ri->integer = 1;
+ }
+
+ /* Set the changed mask */
+ if (changed)
+ p->cmask |= (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_INTERVAL));
+ }
+
+ return 0;
+}
+
+
+static int pcm_plug_hw_params_refine(struct snd_pcm_hw_params *p,
+ struct snd_pcm_hw_params *c)
+{
+ int rc;
+
+ rc = pcm_plug_masks_refine(p, c);
+ if (rc) {
+ fprintf(stderr, "%s: masks refine failed %d\n", __func__, rc);
+ return rc;
+ }
+
+ rc = pcm_plug_interval_refine(p, c);
+ if (rc) {
+ fprintf(stderr, "%s: interval refine failed %d\n", __func__, rc);
+ return rc;
+ }
+
+ /* clear the requested params */
+ p->rmask = 0;
+
+ return rc;
+}
+
+static int __pcm_plug_hrefine(struct pcm_plug_data *plug_data,
+ struct snd_pcm_hw_params *params)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+ struct snd_pcm_hw_params plug_params;
+ int rc;
+
+ memset(&plug_params, 0, sizeof(plug_params));
+ rc = pcm_plug_get_params(plugin, &plug_params);
+ if (rc) {
+ fprintf(stderr, "%s: pcm_plug_get_params failed %d\n",
+ __func__, rc);
+ return -EINVAL;
+ }
+
+ return pcm_plug_hw_params_refine(params, &plug_params);
+
+}
+
+static int pcm_plug_hrefine(struct pcm_plug_data *plug_data,
+ struct snd_pcm_hw_params *params)
+{
+ return __pcm_plug_hrefine(plug_data, params);
+}
+
+static int pcm_plug_interval_select(struct snd_pcm_hw_params *p,
+ unsigned int param, unsigned int select, unsigned int val)
+{
+ struct snd_interval *i;
+
+ if (param < SNDRV_PCM_HW_PARAM_FIRST_INTERVAL ||
+ param > SNDRV_PCM_HW_PARAM_LAST_INTERVAL)
+ return -EINVAL;
+
+ i = &p->intervals[param - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL];
+
+ if (!i->min)
+ return -EINVAL;
+
+ switch (select) {
+
+ case PCM_PLUG_HW_PARAM_SELECT_MIN:
+ i->max = i->min;
+ break;
+
+ case PCM_PLUG_HW_PARAM_SELECT_MAX:
+ i->min = i->max;
+ break;
+
+ case PCM_PLUG_HW_PARAM_SELECT_VAL:
+ i->min = i->max = val;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void pcm_plug_hw_params_set(struct snd_pcm_hw_params *p)
+{
+ unsigned int i, select;
+ unsigned int bw, ch, period_sz, periods;
+ unsigned int val1, val2, offset;
+
+ offset = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL;
+
+ /* Select the min values first */
+ select = PCM_PLUG_HW_PARAM_SELECT_MIN;
+ for (i = 0; i < ARRAY_SIZE(param_list); i++)
+ pcm_plug_interval_select(p, param_list[i], select, 0);
+
+ /* Select calculated values */
+ select = PCM_PLUG_HW_PARAM_SELECT_VAL;
+ bw = (p->intervals[SNDRV_PCM_HW_PARAM_SAMPLE_BITS - offset]).min;
+ ch = (p->intervals[SNDRV_PCM_HW_PARAM_CHANNELS - offset]).min;
+ period_sz = (p->intervals[SNDRV_PCM_HW_PARAM_PERIOD_SIZE - offset]).min;
+ periods = (p->intervals[SNDRV_PCM_HW_PARAM_PERIODS - offset]).min;
+
+ val1 = bw * ch; // frame_bits;
+ pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_FRAME_BITS, select, val1);
+
+ val2 = pcm_plug_frames_to_bytes(period_sz, val1); // period_bytes;
+ pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, select,
+ val2);
+
+ val2 = period_sz * periods; //buffer_size;
+ pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, select, val2);
+
+ val2 = pcm_plug_frames_to_bytes(period_sz * periods, val1); //buffer_bytes;
+ pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, select, val2);
+}
+
+static int pcm_plug_hparams(struct pcm_plug_data *plug_data,
+ struct snd_pcm_hw_params *params)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+ int rc;
+
+ if (plugin->state != PCM_PLUG_STATE_OPEN)
+ return -EBADFD;
+
+ params->rmask = ~0U;
+
+ rc = __pcm_plug_hrefine(plug_data, params);
+ if (rc) {
+ fprintf(stderr, "%s: __pcm_plug_hrefine failed %d\n",
+ __func__, rc);
+ return rc;
+ }
+
+ pcm_plug_hw_params_set(params);
+
+ rc = plug_data->ops->hw_params(plugin, params);
+ if (!rc)
+ plugin->state = PCM_PLUG_STATE_SETUP;
+
+ return rc;
+}
+
+static int pcm_plug_sparams(struct pcm_plug_data *plug_data,
+ struct snd_pcm_sw_params *params)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+
+ if (plugin->state != PCM_PLUG_STATE_SETUP)
+ return -EBADFD;
+
+ return plug_data->ops->sw_params(plugin, params);
+}
+
+static int pcm_plug_sync_ptr(struct pcm_plug_data *plug_data,
+ struct snd_pcm_sync_ptr *sync_ptr)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+
+ 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