aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2021-07-15 01:35:19 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2021-07-15 01:35:19 +0000
commit7f60111300ff18c398d63b472f4462fef65ce629 (patch)
treec1fdd4e6db06429e7a726e1453292e3179f810ef
parentbb72d52a81b8fa58c43681edd057430887b050a4 (diff)
parent43bde0116bb22fa68f3a14a18eef9dc332045f4f (diff)
downloadtinyalsa-android12-mainline-statsd-release.tar.gz
Snap for 7550844 from 43bde0116bb22fa68f3a14a18eef9dc332045f4f to mainline-os-statsd-releaseandroid-mainline-12.0.0_r84android-mainline-12.0.0_r58android12-mainline-statsd-release
Change-Id: Icee3f056aefc2b585722c7917af60d5042297c9d
-rw-r--r--Android.bp25
-rw-r--r--METADATA3
-rw-r--r--OWNERS4
-rw-r--r--include/tinyalsa/asoundlib.h32
-rw-r--r--include/tinyalsa/mixer_plugin.h193
-rw-r--r--include/tinyalsa/pcm_plugin.h104
-rw-r--r--mixer.c484
-rw-r--r--mixer_hw.c122
-rw-r--r--mixer_io.h52
-rw-r--r--mixer_plugin.c505
-rw-r--r--pcm.c159
-rw-r--r--pcm_hw.c146
-rw-r--r--pcm_io.h47
-rw-r--r--pcm_plugin.c799
-rw-r--r--snd_utils.c149
-rw-r--r--snd_utils.h71
16 files changed, 2731 insertions, 164 deletions
diff --git a/Android.bp b/Android.bp
index 090d91c..bbcb86d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,13 +1,36 @@
+package {
+ default_applicable_licenses: ["external_tinyalsa_license"],
+}
+
+// Added automatically by a large-scale-change
+// http://go/android-license-faq
+license {
+ name: "external_tinyalsa_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-BSD",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
cc_library {
name: "libtinyalsa",
host_supported: true,
vendor_available: true,
+ product_available: true,
vndk: {
enabled: true,
},
srcs: [
"mixer.c",
+ "mixer_hw.c",
+ "mixer_plugin.c",
"pcm.c",
+ "pcm_hw.c",
+ "pcm_plugin.c",
+ "snd_utils.c",
],
cflags: ["-Werror", "-Wno-macro-redefined"],
export_include_dirs: ["include"],
@@ -18,6 +41,8 @@ cc_library {
enabled: false,
},
},
+
+ system_shared_libs: ["libc","libdl"],
}
cc_binary {
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..d97975c
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+ license_type: NOTICE
+}
diff --git a/OWNERS b/OWNERS
index fea9c10..e1e3676 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,4 +1,2 @@
-# Default code reviewers picked from top 3 or more developers.
-# Please update this list if you find better candidates.
-muirj@google.com
+dvdli@google.com
gkasten@google.com
diff --git a/include/tinyalsa/asoundlib.h b/include/tinyalsa/asoundlib.h
index 935c8d0..063a256 100644
--- a/include/tinyalsa/asoundlib.h
+++ b/include/tinyalsa/asoundlib.h
@@ -153,6 +153,31 @@ enum mixer_ctl_type {
MIXER_CTL_TYPE_MAX,
};
+#define CTL_ELEM_ID_NAME_MAXLEN 44
+
+typedef int ctl_elem_iface_t;
+
+struct ctl_elem_id {
+ unsigned int numid;
+ ctl_elem_iface_t iface;
+ unsigned int device;
+ unsigned int subdevice;
+ unsigned char name[CTL_ELEM_ID_NAME_MAXLEN];
+ unsigned int index;
+};
+
+
+struct ctl_event {
+ int type;
+ union {
+ struct {
+ unsigned int mask;
+ struct ctl_elem_id id;
+ } elem;
+ unsigned char data8[60];
+ } data;
+};
+
/* Open and close a stream */
struct pcm *pcm_open(unsigned int card, unsigned int device,
unsigned int flags, struct pcm_config *config);
@@ -164,13 +189,13 @@ struct pcm_params *pcm_params_get(unsigned int card, unsigned int device,
unsigned int flags);
void pcm_params_free(struct pcm_params *pcm_params);
-struct pcm_mask *pcm_params_get_mask(struct pcm_params *pcm_params,
+struct pcm_mask *pcm_params_get_mask(const struct pcm_params *pcm_params,
enum pcm_param param);
-unsigned int pcm_params_get_min(struct pcm_params *pcm_params,
+unsigned int pcm_params_get_min(const struct pcm_params *pcm_params,
enum pcm_param param);
void pcm_params_set_min(struct pcm_params *pcm_params,
enum pcm_param param, unsigned int val);
-unsigned int pcm_params_get_max(struct pcm_params *pcm_params,
+unsigned int pcm_params_get_max(const struct pcm_params *pcm_params,
enum pcm_param param);
void pcm_params_set_max(struct pcm_params *pcm_params,
enum pcm_param param, unsigned int val);
@@ -317,6 +342,7 @@ int mixer_ctl_get_range_max(struct mixer_ctl *ctl);
int mixer_subscribe_events(struct mixer *mixer, int subscribe);
int mixer_wait_event(struct mixer *mixer, int timeout);
int mixer_consume_event(struct mixer *mixer);
+int mixer_read_event(struct mixer *mixer, struct ctl_event *ev);
#if defined(__cplusplus)
} /* extern "C" */
diff --git a/include/tinyalsa/mixer_plugin.h b/include/tinyalsa/mixer_plugin.h
new file mode 100644
index 0000000..e8ef91a
--- /dev/null
+++ b/include/tinyalsa/mixer_plugin.h
@@ -0,0 +1,193 @@
+/*
+** Copyright (c) 2019, The Linux Foundation. All rights reserved.
+**
+** 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 __MIXER_PLUGIN_H__
+#define __MIXER_PLUGIN_H__
+
+#define MIXER_PLUGIN_OPEN_FN(name) \
+ int name##_open(struct mixer_plugin **plugin, \
+ unsigned int card)
+
+#define MIXER_PLUGIN_OPEN_FN_PTR() \
+ int (*mixer_plugin_open_fn) (struct mixer_plugin **plugin, \
+ unsigned int card) \
+
+struct mixer_plugin;
+
+typedef void (*event_callback)(struct mixer_plugin *);
+
+struct mixer_plugin_ops {
+ void (*close) (struct mixer_plugin **plugin);
+ int (*subscribe_events) (struct mixer_plugin *plugin,
+ event_callback event_cb);
+ ssize_t (*read_event) (struct mixer_plugin *plugin,
+ struct ctl_event *ev, size_t size);
+};
+
+struct snd_control {
+ 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;
+ struct mixer_plugin_ops *ops;
+ 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;
+};
+
+/* 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 }
+
+#define SND_CONTROL_ENUM(cname, cget, cput, cenum, priv_val, priv_data) \
+ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, \
+ .name = cname, .value = &cenum, .get = cget, .put = cput, \
+ .private_value = priv_val, .private_data = priv_data, \
+ }
+
+#define SND_CONTROL_BYTES(cname, cget, cput, cbytes, priv_val, priv_data) \
+ { \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .type = SNDRV_CTL_ELEM_TYPE_BYTES, \
+ .name = cname, .value = &cbytes, .get = cget, .put = cput, \
+ .private_value = priv_val, .private_data = priv_data, \
+ }
+
+#define SND_CONTROL_INTEGER(cname, cget, cput, cint, priv_val, priv_data) \
+ { \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .type = SNDRV_CTL_ELEM_TYPE_INTEGER, \
+ .name = cname, .value = &cint, .get = cget, .put = cput, \
+ .private_value = priv_val, .private_data = priv_data, \
+ }
+
+#define SND_CONTROL_TLV_BYTES(cname, cbytes, priv_val, priv_data) \
+ { \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE, \
+ .type = SNDRV_CTL_ELEM_TYPE_BYTES, \
+ .name = cname, .value = &cbytes, \
+ .private_value = priv_val, .private_data = priv_data, \
+ }
+
+/* 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; \
+ }
+#endif /* end of __MIXER_PLUGIN_H__ */
diff --git a/include/tinyalsa/pcm_plugin.h b/include/tinyalsa/pcm_plugin.h
new file mode 100644
index 0000000..f1708b1
--- /dev/null
+++ b/include/tinyalsa/pcm_plugin.h
@@ -0,0 +1,104 @@
+/* pcm_plugin.h
+** Copyright (c) 2019, The Linux Foundation. All rights reserved.
+**
+** 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 __PCM_PLUGIN_H__
+#define __PCM_PLUGIN_H__
+
+#include <poll.h>
+
+#define PCM_PLUGIN_OPEN_FN(name) \
+ int name##_open(struct pcm_plugin **plugin, \
+ unsigned int card, \
+ unsigned int device, \
+ int mode)
+
+#define PCM_PLUGIN_OPEN_FN_PTR() \
+ int (*plugin_open_fn) (struct pcm_plugin **plugin, \
+ unsigned int card, \
+ unsigned int device, \
+ int mode);
+
+struct pcm_plugin;
+
+struct pcm_plugin_ops {
+ int (*close) (struct pcm_plugin *plugin);
+ int (*hw_params) (struct pcm_plugin *plugin,
+ struct snd_pcm_hw_params *params);
+ int (*sw_params) (struct pcm_plugin *plugin,
+ struct snd_pcm_sw_params *params);
+ int (*sync_ptr) (struct pcm_plugin *plugin,
+ struct snd_pcm_sync_ptr *sync_ptr);
+ int (*writei_frames) (struct pcm_plugin *plugin,
+ struct snd_xferi *x);
+ int (*readi_frames) (struct pcm_plugin *plugin,
+ struct snd_xferi *x);
+ int (*ttstamp) (struct pcm_plugin *plugin,
+ int *tstamp);
+ int (*prepare) (struct pcm_plugin *plugin);
+ int (*start) (struct pcm_plugin *plugin);
+ int (*drop) (struct pcm_plugin *plugin);
+ int (*ioctl) (struct pcm_plugin *plugin,
+ int cmd, void *arg);
+ void* (*mmap) (struct pcm_plugin *plugin, void *addr, size_t length, int prot,
+ int flags, off_t offset);
+ int (*munmap) (struct pcm_plugin *plugin, void *addr, size_t length);
+ int (*poll) (struct pcm_plugin *plugin, struct pollfd *pfd, nfds_t nfds,
+ int timeout);
+};
+
+struct pcm_plugin_min_max {
+ unsigned int min;
+ unsigned int max;
+};
+
+struct pcm_plugin_hw_constraints {
+ uint64_t access;
+ /* As of this implementation ALSA supports 52 formats */
+ uint64_t format;
+ struct pcm_plugin_min_max bit_width;
+ struct pcm_plugin_min_max channels;
+ struct pcm_plugin_min_max rate;
+ struct pcm_plugin_min_max periods;
+ struct pcm_plugin_min_max period_bytes;
+};
+
+struct pcm_plugin {
+ unsigned int card;
+
+ struct pcm_plugin_ops *ops;
+ struct pcm_plugin_hw_constraints *constraints;
+
+ void *node;
+ int mode;
+ void *priv;
+
+ unsigned int state;
+};
+
+#endif /* end of __PCM_PLUGIN_H__ */
diff --git a/mixer.c b/mixer.c
index f3fdb62..7b8540a 100644
--- a/mixer.c
+++ b/mixer.c
@@ -40,6 +40,7 @@
#include <sys/ioctl.h>
#include <linux/ioctl.h>
+
#define __force
#define __bitwise
#define __user
@@ -50,122 +51,262 @@
#endif
#include <tinyalsa/asoundlib.h>
+#include "mixer_io.h"
struct mixer_ctl {
- struct mixer *mixer;
+ struct mixer_ctl_group *grp;
struct snd_ctl_elem_info *info;
char **ename;
bool info_retrieved;
};
-struct mixer {
- int fd;
- struct snd_ctl_card_info card_info;
+struct mixer_ctl_group {
struct snd_ctl_elem_info *elem_info;
struct mixer_ctl *ctl;
unsigned int count;
+ int event_cnt;
+
+ struct mixer_ops *ops;
+ void *data;
};
-void mixer_close(struct mixer *mixer)
+struct mixer {
+ int fd;
+ struct snd_ctl_card_info card_info;
+
+ /* hardware/physical mixer control group */
+ struct mixer_ctl_group *hw_grp;
+
+ /*
+ * Virutal mixer control group.
+ * Currently supports one virtual mixer (.so)
+ * per card. Could be extended to multiple
+ */
+ struct mixer_ctl_group *virt_grp;
+
+ unsigned int total_ctl_count;
+};
+
+static void mixer_grp_close(struct mixer_ctl_group *grp)
{
- unsigned int n,m;
+ unsigned int n, m;
- if (!mixer)
+ if (!grp)
return;
- if (mixer->fd >= 0)
- close(mixer->fd);
-
- if (mixer->ctl) {
- for (n = 0; n < mixer->count; n++) {
- if (mixer->ctl[n].ename) {
- unsigned int max = mixer->ctl[n].info->value.enumerated.items;
+ if (grp->ctl) {
+ for (n = 0; n < grp->count; n++) {
+ if (grp->ctl[n].ename) {
+ unsigned int max = grp->ctl[n].info->value.enumerated.items;
for (m = 0; m < max; m++)
- free(mixer->ctl[n].ename[m]);
- free(mixer->ctl[n].ename);
+ free(grp->ctl[n].ename[m]);
+ free(grp->ctl[n].ename);
}
}
- free(mixer->ctl);
+ free(grp->ctl);
}
- if (mixer->elem_info)
- free(mixer->elem_info);
+ if (grp->elem_info)
+ free(grp->elem_info);
+
+ free(grp);
+}
+
+void mixer_close(struct mixer *mixer)
+{
+ if (!mixer)
+ return;
+
+ if (mixer->fd >= 0 && mixer->hw_grp)
+ mixer->hw_grp->ops->close(mixer->hw_grp->data);
+ mixer_grp_close(mixer->hw_grp);
+
+ if (mixer->virt_grp)
+ mixer->virt_grp->ops->close(mixer->virt_grp->data);
+ mixer_grp_close(mixer->virt_grp);
free(mixer);
/* TODO: verify frees */
}
-struct mixer *mixer_open(unsigned int card)
+static int mixer_grp_open(struct mixer_ctl_group **ctl_grp,
+ struct mixer_ops *ops,
+ void *data, int *num_ctls_in_grp)
{
+ struct mixer_ctl_group *grp;
struct snd_ctl_elem_list elist;
struct snd_ctl_elem_id *eid = NULL;
- struct mixer *mixer = NULL;
unsigned int n;
- int fd;
- char fn[256];
+ int ret;
- snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);
- fd = open(fn, O_RDWR);
- if (fd < 0)
- return 0;
+ grp = calloc(1, sizeof(*grp));
+ if (!grp)
+ return -ENOMEM;
memset(&elist, 0, sizeof(elist));
- if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
- goto fail;
-
- mixer = calloc(1, sizeof(*mixer));
- if (!mixer)
- goto fail;
-
- mixer->ctl = calloc(elist.count, sizeof(struct mixer_ctl));
- mixer->elem_info = calloc(elist.count, sizeof(struct snd_ctl_elem_info));
- if (!mixer->ctl || !mixer->elem_info)
- goto fail;
+ ret = ops->ioctl(data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist);
+ if (ret < 0)
+ goto err_get_elem_list;
- if (ioctl(fd, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info) < 0)
- goto fail;
+ grp->ctl = calloc(elist.count, sizeof(struct mixer_ctl));
+ grp->elem_info = calloc(elist.count, sizeof(struct snd_ctl_elem_info));
+ if (!grp->ctl || !grp->elem_info) {
+ ret = -ENOMEM;
+ goto err_ctl_alloc;
+ }
- eid = calloc(elist.count, sizeof(struct snd_ctl_elem_id));
- if (!eid)
- goto fail;
+ eid = calloc(elist.count, sizeof(*eid));
+ if (!eid) {
+ ret = -ENOMEM;
+ goto err_ctl_alloc;
+ }
- mixer->count = elist.count;
- mixer->fd = fd;
- elist.space = mixer->count;
+ grp->count = elist.count;
+ elist.space = grp->count;
elist.pids = eid;
- if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
- goto fail;
+ ret = ops->ioctl(data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist);
+ if (ret < 0)
+ goto err_ctl_alloc;
- for (n = 0; n < mixer->count; n++) {
- struct mixer_ctl *ctl = mixer->ctl + n;
+ for (n = 0; n < grp->count; n++) {
+ struct mixer_ctl *ctl = grp->ctl + n;
- ctl->mixer = mixer;
- ctl->info = mixer->elem_info + n;
+ ctl->grp = grp;
+ ctl->info = grp->elem_info + n;
ctl->info->id.numid = eid[n].numid;
strncpy((char *)ctl->info->id.name, (char *)eid[n].name,
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
ctl->info->id.name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0;
}
+ grp->data = data;
+ grp->ops = ops;
+ *ctl_grp = grp;
+ *num_ctls_in_grp = grp->count;
+
+ free(eid);
+ return 0;
+
+err_ctl_alloc:
+
free(eid);
+ free(grp->elem_info);
+ free(grp->ctl);
+
+err_get_elem_list:
+
+ free(grp);
+ return ret;
+
+}
+
+static int mixer_do_hw_open(struct mixer *mixer, unsigned int card)
+{
+ struct mixer_ops *ops;
+ void *data;
+ int fd, ret, num_grp_ctls = 0;
+
+ mixer->fd = -1;
+ fd = mixer_hw_open(card, &data, &ops);
+ if (fd < 0)
+ return fd;
+
+ ret = ops->ioctl(data, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info);
+ if (ret < 0)
+ goto err_card_info;
+
+ ret = mixer_grp_open(&mixer->hw_grp, ops, data, &num_grp_ctls);
+ if (ret < 0)
+ goto err_card_info;
+
+ mixer->total_ctl_count += num_grp_ctls;
+
+ mixer->fd = fd;
+ return 0;
+
+err_card_info:
+ ops->close(data);
+ return ret;
+
+}
+
+static int mixer_do_plugin_open(struct mixer *mixer, unsigned int card,
+ int is_hw_open_failed)
+{
+ struct mixer_ops *ops;
+ void *data;
+ int ret, num_grp_ctls = 0;
+
+ ret = mixer_plugin_open(card, &data, &ops);
+ if (ret < 0)
+ return ret;
+
+ /* Get card_info if hw_open failed */
+ if (is_hw_open_failed) {
+ ret = ops->ioctl(data, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info);
+ if (ret < 0)
+ goto err_card_info;
+ }
+
+ ret = mixer_grp_open(&mixer->virt_grp, ops, data, &num_grp_ctls);
+ if (ret < 0)
+ goto err_card_info;
+
+ mixer->total_ctl_count += num_grp_ctls;
+ return 0;
+
+err_card_info:
+ ops->close(data);
+ return ret;
+
+}
+
+struct mixer *mixer_open(unsigned int card)
+{
+ struct mixer *mixer = NULL;
+ int h_status, v_status;
+
+ mixer = calloc(1, sizeof(*mixer));
+ if (!mixer)
+ goto fail;
+
+ /* open hardware node first */
+ h_status = mixer_do_hw_open(mixer, card);
+
+ /*
+ * open the virtual node even if hw_open fails
+ * since hw_open is expected to fail for virtual cards
+ * for which kernel does not register mixer node
+ */
+ //TODO: open virtual node only if mixer is defined under snd-card-def
+ v_status = mixer_do_plugin_open(mixer, card, h_status);
+
+ /* Fail mixer_open if both hw and plugin nodes cannot be opened */
+ if (h_status < 0 && v_status < 0)
+ goto fail;
+
return mixer;
fail:
- /* TODO: verify frees in failure case */
- if (eid)
- free(eid);
if (mixer)
mixer_close(mixer);
- else if (fd >= 0)
- close(fd);
+
return 0;
}
static bool mixer_ctl_get_elem_info(struct mixer_ctl* ctl)
{
+ struct mixer_ctl_group *grp;
+ unsigned int i;
+
+ if (!ctl || !ctl->grp)
+ return false;
+
+ grp = ctl->grp;
if (!ctl->info_retrieved) {
- if (ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, ctl->info) < 0)
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO,
+ ctl->info) < 0)
return false;
ctl->info_retrieved = true;
}
@@ -178,11 +319,11 @@ static bool mixer_ctl_get_elem_info(struct mixer_ctl* ctl)
if (!enames)
return false;
- for (unsigned int i = 0; i < ctl->info->value.enumerated.items; i++) {
+ for (i = 0; i < ctl->info->value.enumerated.items; i++) {
memset(&tmp, 0, sizeof(tmp));
tmp.id.numid = ctl->info->id.numid;
tmp.value.enumerated.item = i;
- 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[i] = strdup(tmp.value.enumerated.name);
if (!enames[i])
@@ -206,17 +347,35 @@ unsigned int mixer_get_num_ctls(struct mixer *mixer)
if (!mixer)
return 0;
- return mixer->count;
+ return mixer->total_ctl_count;
+}
+
+static int mixer_grp_get_count(struct mixer_ctl_group *grp)
+{
+ if (!grp)
+ return 0;
+
+ return grp->count;
}
struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id)
{
struct mixer_ctl *ctl;
+ unsigned int hw_ctl_count, virt_ctl_count;
+
+ if (!mixer || (id >= mixer->total_ctl_count))
+ return NULL;
+
+ hw_ctl_count = mixer_grp_get_count(mixer->hw_grp);
+ virt_ctl_count = mixer_grp_get_count(mixer->virt_grp);
- if (!mixer || (id >= mixer->count))
+ if (id < hw_ctl_count)
+ ctl = mixer->hw_grp->ctl + id;
+ else if ((id - hw_ctl_count) < virt_ctl_count)
+ ctl = mixer->virt_grp->ctl + (id - hw_ctl_count);
+ else
return NULL;
- ctl = mixer->ctl + id;
if (!mixer_ctl_get_elem_info(ctl))
return NULL;
@@ -225,21 +384,42 @@ struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id)
struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name)
{
+ struct mixer_ctl_group *grp;
unsigned int n;
+ int hw_ctl_count;
if (!mixer)
return NULL;
- for (n = 0; n < mixer->count; n++)
- if (!strcmp(name, (char*) mixer->elem_info[n].id.name))
- return mixer_get_ctl(mixer, n);
+ hw_ctl_count = mixer_grp_get_count(mixer->hw_grp);
+ if (mixer->hw_grp) {
+ grp = mixer->hw_grp;
+
+ for (n = 0; n < grp->count; n++)
+ if (!strcmp(name, (char*) grp->elem_info[n].id.name))
+ return mixer_get_ctl(mixer, n);
+ }
+
+ if (mixer->virt_grp) {
+ grp = mixer->virt_grp;
+
+ for (n = 0; n < grp->count; n++)
+ if (!strcmp(name, (char*) grp->elem_info[n].id.name))
+ return mixer_get_ctl(mixer, n + hw_ctl_count);
+ }
return NULL;
}
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 || !ctl->grp)
+ return;
+
+ grp = ctl->grp;
+ grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, ctl->info);
}
const char *mixer_ctl_get_name(struct mixer_ctl *ctl)
@@ -332,15 +512,17 @@ int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent)
int mixer_ctl_get_value(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))
+ if (!ctl || (id >= ctl->info->count) || !ctl->grp)
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;
@@ -371,15 +553,17 @@ int mixer_ctl_is_access_tlv_rw(struct mixer_ctl *ctl)
int mixer_ctl_get_array(struct mixer_ctl *ctl, void *array, size_t count)
{
+ struct mixer_ctl_group *grp;
struct snd_ctl_elem_value ev;
int ret = 0;
size_t size;
void *source;
size_t total_count;
- if ((!ctl) || !count || !array)
+ if ((!ctl) || !count || !array || !ctl->grp)
return -EINVAL;
+ grp = ctl->grp;
total_count = ctl->info->count;
if ((ctl->info->type == SNDRV_CTL_ELEM_TYPE_BYTES) &&
@@ -397,7 +581,7 @@ int mixer_ctl_get_array(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]);
@@ -417,7 +601,7 @@ int mixer_ctl_get_array(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);
@@ -426,7 +610,7 @@ int mixer_ctl_get_array(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]);
@@ -450,15 +634,17 @@ int mixer_ctl_get_array(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))
+ if (!ctl || (id >= ctl->info->count) || !ctl->grp)
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;
@@ -483,19 +669,21 @@ 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);
}
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;
size_t total_count;
- if ((!ctl) || !count || !array)
+ if ((!ctl) || !count || !array || !ctl->grp)
return -EINVAL;
+ grp = ctl->grp;
total_count = ctl->info->count;
if ((ctl->info->type == SNDRV_CTL_ELEM_TYPE_BYTES) &&
@@ -531,7 +719,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;
@@ -552,7 +740,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);
}
int mixer_ctl_get_range_min(struct mixer_ctl *ctl)
@@ -591,20 +779,22 @@ 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;
- if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED))
+ if (!ctl || (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED) || !ctl->grp)
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;
@@ -623,8 +813,22 @@ int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string)
*/
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->hw_grp) {
+ grp = mixer->hw_grp;
+ if (!subscribe)
+ grp->event_cnt = 0;
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0)
+ return -1;
+ }
+
+ if (mixer->virt_grp) {
+ grp = mixer->virt_grp;
+ if (!subscribe)
+ grp->event_cnt = 0;
+ if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0)
+ return -1;
}
return 0;
}
@@ -639,22 +843,58 @@ int mixer_subscribe_events(struct mixer *mixer, int subscribe)
*/
int mixer_wait_event(struct mixer *mixer, int timeout)
{
- struct pollfd pfd;
+ struct pollfd pfd[2];
+ struct mixer_ctl_group *grp;
+ int count = 0, num_fds = 0, i;
+
+ memset(pfd, 0, sizeof(struct pollfd) * 2);
+
+ if (mixer->fd >= 0)
+ num_fds++;
+
+ if (mixer->virt_grp)
+ num_fds++;
+
+
+ /* TODO wait events for virt fd */
+ if (mixer->fd >= 0) {
+ pfd[count].fd = mixer->fd;
+ pfd[count].events = POLLIN | POLLOUT | POLLERR | POLLNVAL;
+ count++;
+ }
+
+ if (mixer->virt_grp) {
+ grp = mixer->virt_grp;
+ if (!grp->ops->get_poll_fd(grp->data, pfd, count)) {
+ pfd[count].events = POLLIN | POLLERR | POLLNVAL;
+ count++;
+ }
+ }
- pfd.fd = mixer->fd;
- pfd.events = POLLIN | POLLOUT | POLLERR | POLLNVAL;
+ 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->hw_grp;
+ grp->event_cnt++;
+ } else {
+ grp = mixer->virt_grp;
+ grp->event_cnt++;
+ }
+ return 1;
+ }
+ }
}
}
@@ -668,13 +908,69 @@ int mixer_wait_event(struct mixer *mixer, int timeout)
* @returns 0 on success. -errno on failure.
* @ingroup libtinyalsa-mixer
*/
-int mixer_consume_event(struct mixer *mixer) {
+int mixer_consume_event(struct mixer *mixer)
+{
struct snd_ctl_event ev;
- ssize_t count = read(mixer->fd, &ev, sizeof(ev));
+ struct mixer_ctl_group *grp;
+ ssize_t count = 0;
+
// Exporting the actual event would require exposing snd_ctl_event
// via the header file, and all associated structs.
// The events generally tell you exactly which value changed,
// but reading values you're interested isn't hard and simplifies
// the interface greatly.
- return (count >= 0) ? 0 : -errno;
+ if (mixer->hw_grp) {
+ grp = mixer->hw_grp;
+ if (grp->event_cnt) {
+ grp->event_cnt--;
+ count = grp->ops->read_event(grp->data, &ev, sizeof(ev));
+ return (count >= 0) ? 0 : -errno;
+ }
+ }
+
+ if (mixer->virt_grp) {
+ grp = mixer->virt_grp;
+ if (grp->event_cnt) {
+ grp->event_cnt--;
+ count += grp->ops->read_event(grp->data, &ev, sizeof(ev));
+ return (count >= 0) ? 0 : -errno;
+ }
+ }
+ return 0;
+}
+
+/** Read a mixer event.
+ * If mixer_subscribe_events has been called,
+ * mixer_wait_event will identify when a control value has changed.
+ * This function will read and clear a single event from the mixer
+ * so that further events can be alerted.
+ *
+ * @param mixer A mixer handle.
+ * @param ev snd_ctl_event pointer where event needs to be read
+ * @returns 0 on success. -errno on failure.
+ * @ingroup libtinyalsa-mixer
+ */
+int mixer_read_event(struct mixer *mixer, struct ctl_event *ev)
+{
+ struct mixer_ctl_group *grp;
+ ssize_t count = 0;
+
+ if (mixer->hw_grp) {
+ grp = mixer->hw_grp;
+ if (grp->event_cnt) {
+ grp->event_cnt--;
+ count = grp->ops->read_event(grp->data, (struct snd_ctl_event *)ev, sizeof(*ev));
+ return (count >= 0) ? 0 : -errno;
+ }
+ }
+
+ if (mixer->virt_grp) {
+ grp = mixer->virt_grp;
+ if (grp->event_cnt) {
+ grp->event_cnt--;
+ count = grp->ops->read_event(grp->data, (struct snd_ctl_event *)ev, sizeof(*ev));
+ return (count >= 0) ? 0 : -errno;
+ }
+ }
+ return 0;
}
diff --git a/mixer_hw.c b/mixer_hw.c
new file mode 100644
index 0000000..9e01647
--- /dev/null
+++ b/mixer_hw.c
@@ -0,0 +1,122 @@
+/* mixer_hw.c
+**
+** Copyright (c) 2019, The Linux Foundation. All rights reserved.
+**
+** 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"
+
+struct mixer_hw_data {
+ unsigned int card;
+ int fd;
+};
+
+static void mixer_hw_close(void *data)
+{
+ struct mixer_hw_data *hw_data = data;
+
+ if (!hw_data)
+ return;
+
+ if (hw_data->fd >= 0)
+ close(hw_data->fd);
+
+ hw_data->fd = -1;
+ free(hw_data);
+ hw_data = NULL;
+}
+
+static int mixer_hw_ioctl(void *data, unsigned int cmd, ...)
+{
+ struct mixer_hw_data *hw_data = data;
+ va_list ap;
+ void *arg;
+
+ va_start(ap, cmd);
+ arg = va_arg(ap, void *);
+ va_end(ap);
+
+ return ioctl(hw_data->fd, cmd, arg);
+}
+
+static ssize_t mixer_hw_read_event(void *data, struct snd_ctl_event *ev,
+ size_t size)
+{
+ struct mixer_hw_data *hw_data = data;
+
+ return read(hw_data->fd, ev, size);
+}
+
+static struct mixer_ops mixer_hw_ops = {
+ .close = mixer_hw_close,
+ .get_poll_fd = NULL,
+ .read_event = mixer_hw_read_event,
+ .ioctl = mixer_hw_ioctl,
+};
+
+int mixer_hw_open(unsigned int card, void **data,
+ 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) {
+ close(fd);
+ return -1;
+ }
+
+ hw_data->card = card;
+ hw_data->fd = fd;
+ *data = hw_data;
+ *ops = &mixer_hw_ops;
+
+ return fd;
+}
diff --git a/mixer_io.h b/mixer_io.h
new file mode 100644
index 0000000..77daae0
--- /dev/null
+++ b/mixer_io.h
@@ -0,0 +1,52 @@
+/* mixer_io.h
+**
+** Copyright (c) 2019, The Linux Foundation. All rights reserved.
+**
+** 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 __MIXER_H__
+#define __MIXER_H__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sound/asound.h>
+
+struct mixer_ops;
+
+int mixer_hw_open(unsigned int card, void **data,
+ struct mixer_ops **ops);
+int mixer_plugin_open(unsigned int card, void **data,
+ 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 /* end of __MIXER_H__ */
diff --git a/mixer_plugin.c b/mixer_plugin.c
new file mode 100644
index 0000000..015120a
--- /dev/null
+++ b/mixer_plugin.c
@@ -0,0 +1,505 @@
+/* mixer_plugin.c
+**
+** Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
+**
+** 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 <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 <sys/eventfd.h>
+#include <sys/ioctl.h>
+
+#include <linux/ioctl.h>
+#include <sound/asound.h>
+
+#include <tinyalsa/asoundlib.h>
+#include <tinyalsa/mixer_plugin.h>
+#include "snd_utils.h"
+
+#include "mixer_io.h"
+
+struct mixer_plug_data {
+ int card;
+ void *mixer_node;
+
+ struct mixer_plugin *plugin;
+ void *dl_hdl;
+ MIXER_PLUGIN_OPEN_FN_PTR();
+};
+
+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) {
+ printf("%s: invalid offset %u\n", __func__, offset);
+ return -EINVAL;
+ }
+
+ ctl = plugin->controls + offset;
+ id->numid = offset;
+ id->iface = ctl->iface;
+
+ strncpy((char *)id->name, (char *)ctl->name,
+ sizeof(id->name));
+
+ return 0;
+}
+
+static int mixer_plug_info_enum(struct snd_control *ctl,
+ struct snd_ctl_elem_info *einfo)
+{
+ struct snd_value_enum *val = ctl->value;
+
+ einfo->count = 1;
+ einfo->value.enumerated.items = val->items;
+
+ if (einfo->value.enumerated.item > val->items)
+ return -EINVAL;
+
+ strncpy(einfo->value.enumerated.name,
+ val->texts[einfo->value.enumerated.item],
+ sizeof(einfo->value.enumerated.name));
+
+ return 0;
+}
+
+static int mixer_plug_info_bytes(struct snd_control *ctl,
+ struct snd_ctl_elem_info *einfo)
+{
+ struct snd_value_bytes *val;
+ struct snd_value_tlv_bytes *val_tlv;
+
+ if (ctl->access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) {
+ val_tlv = ctl->value;
+ einfo->count = val_tlv->size;
+ } else {
+ val = ctl->value;
+ einfo->count = val->size;
+ }
+
+ return 0;
+}
+
+static int mixer_plug_info_integer(struct snd_control *ctl,
+ struct snd_ctl_elem_info *einfo)
+{
+ struct snd_value_int *val = ctl->value;
+
+ einfo->count = val->count;
+ einfo->value.integer.min = val->min;
+ einfo->value.integer.max = val->max;
+ einfo->value.integer.step = val->step;
+
+ return 0;
+}
+
+void mixer_plug_notifier_cb(struct mixer_plugin *plugin)
+{
+ plugin->event_cnt++;
+ eventfd_write(plugin->eventfd, 1);
+}
+
+/* In consume_event/read, do not call eventfd_read until all events are read from list.
+ This will make poll getting unblocked until all events are read */
+static ssize_t mixer_plug_read_event(void *data, struct snd_ctl_event *ev, size_t size)
+{
+ struct mixer_plug_data *plug_data = data;
+ struct mixer_plugin *plugin = plug_data->plugin;
+ eventfd_t evfd;
+ ssize_t result = 0;
+
+ result = plugin->ops->read_event(plugin, (struct ctl_event *)ev, size);
+
+ if (result > 0) {
+ plugin->event_cnt -= result / sizeof(struct snd_ctl_event);
+ if (plugin->event_cnt <= 0) {
+ 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) {
+ plugin->ops->subscribe_events(plugin, &mixer_plug_notifier_cb);
+ } else if (plugin->subscribed && !*subscribe) {
+ plugin->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:
+ printf("%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", strlen("card_id") + 1);
+ memcpy(card_info->driver, "mymixer-so-name", strlen("mymixer-so-name") + 1);
+ memcpy(card_info->name, "card-name", strlen("card-name") + 1);
+ memcpy(card_info->longname, "card-name", strlen("card-name") + 1);
+ memcpy(card_info->mixername, "mixer-name", strlen("mixer-name") + 1);
+
+ printf("%s: card = %d\n", __func__, plug_data->card);
+
+ return 0;
+}
+
+static void mixer_plug_close(void *data)
+{
+ struct mixer_plug_data *plug_data = data;
+ struct mixer_plugin *plugin = plug_data->plugin;
+ eventfd_t evfd;
+
+ if (plugin->event_cnt)
+ eventfd_read(plugin->eventfd, &evfd);
+
+ plugin->ops->close(&plugin);
+ dlclose(plug_data->dl_hdl);
+ snd_utils_put_dev_node(plug_data->mixer_node);
+ 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 struct mixer_ops mixer_plug_ops = {
+ .close = mixer_plug_close,
+ .get_poll_fd = mixer_plug_get_poll_fd,
+ .read_event = mixer_plug_read_event,
+ .ioctl = mixer_plug_ioctl,
+};
+
+int mixer_plugin_open(unsigned int card, void **data,
+ struct mixer_ops **ops)
+{
+ struct mixer_plug_data *plug_data;
+ struct mixer_plugin *plugin = NULL;
+ void *dl_hdl;
+ char *name, *so_name;
+ char *open_fn_name, token[80], *token_saveptr;
+ int ret;
+
+ plug_data = calloc(1, sizeof(*plug_data));
+ if (!plug_data)
+ return -ENOMEM;
+
+ /* mixer id is fixed to 1 in snd-card-def xml */
+ plug_data->mixer_node = snd_utils_get_dev_node(card, 1, NODE_MIXER);
+ if (!plug_data->mixer_node) {
+ /* Do not print error here.
+ * It is valid for card to not have virtual mixer node
+ */
+ goto err_free_plug_data;
+ }
+
+ 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_put_dev_node;
+
+ }
+
+ dl_hdl = dlopen(so_name, RTLD_NOW);
+ if (!dl_hdl) {
+ fprintf(stderr, "%s: unable to open %s\n",
+ __func__, so_name);
+ goto err_put_dev_node;
+ }
+
+ sscanf(so_name, "lib%s", token);
+ token_saveptr = token;
+ name = strtok_r(token, ".", &token_saveptr);
+ if (!name) {
+ fprintf(stderr, "%s: invalid library name\n", __func__);
+ goto err_dl_hdl;
+ }
+
+ open_fn_name = calloc(1, strlen(name) + strlen("_open") + 1);
+ if (!open_fn_name) {
+ ret = -ENOMEM;
+ goto err_dl_hdl;
+ }
+
+ strncpy(open_fn_name, name, strlen(name) + 1);
+ strcat(open_fn_name, "_open");
+
+ printf("%s - %s\n", __func__, open_fn_name);
+
+ plug_data->mixer_plugin_open_fn = dlsym(dl_hdl, open_fn_name);
+ if (!plug_data->mixer_plugin_open_fn) {
+ fprintf(stderr, "%s: dlsym open fn failed: %s\n",
+ __func__, dlerror());
+ goto err_open_fn_name;
+ }
+ ret = plug_data->mixer_plugin_open_fn(&plugin, card);
+ if (ret) {
+ fprintf(stderr, "%s: failed to open plugin, err: %d\n",
+ __func__, ret);
+ goto err_open_fn_name;
+ }
+
+ 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;
+
+ printf("%s: card = %d\n", __func__, plug_data->card);
+
+ free(open_fn_name);
+ return 0;
+
+err_open_fn_name:
+ free(open_fn_name);
+
+err_dl_hdl:
+ dlclose(dl_hdl);
+
+err_put_dev_node:
+ snd_utils_put_dev_node(plug_data->mixer_node);
+
+err_free_plug_data:
+
+ free(plug_data);
+ return -1;
+}
diff --git a/pcm.c b/pcm.c
index 4ae321b..1709be9 100644
--- a/pcm.c
+++ b/pcm.c
@@ -47,6 +47,13 @@
#include <sound/asound.h>
#include <tinyalsa/asoundlib.h>
+#include "pcm_io.h"
+#include "snd_utils.h"
+
+enum {
+ PCM_NODE_TYPE_HW = 0,
+ PCM_NODE_TYPE_PLUGIN,
+};
#define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL
@@ -123,6 +130,9 @@ static const char * const format_lookup[] = {
#endif
};
+extern struct pcm_ops hw_ops;
+extern struct pcm_ops plug_ops;
+
/* refer to SNDRV_PCM_SUBFORMAT_##index in sound/asound.h. */
static const char * const subformat_lookup[] = {
"STD",
@@ -257,6 +267,10 @@ struct pcm {
unsigned int noirq_frames_per_msec;
int wait_for_avail_min;
unsigned int subdevice;
+
+ struct pcm_ops *ops;
+ void *data;
+ void *snd_node;
};
unsigned int pcm_get_buffer_size(struct pcm *pcm)
@@ -336,7 +350,8 @@ unsigned int pcm_frames_to_bytes(struct pcm *pcm, unsigned int frames)
static int pcm_sync_ptr(struct pcm *pcm, int flags) {
if (pcm->sync_ptr) {
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)
return -1;
}
return 0;
@@ -348,19 +363,19 @@ static int pcm_hw_mmap_status(struct pcm *pcm) {
return 0;
int page_size = sysconf(_SC_PAGE_SIZE);
- pcm->mmap_status = mmap(NULL, page_size, PROT_READ, MAP_FILE | MAP_SHARED,
- pcm->fd, SNDRV_PCM_MMAP_OFFSET_STATUS);
+ pcm->mmap_status = pcm->ops->mmap(pcm->data, NULL, page_size, PROT_READ, MAP_FILE | MAP_SHARED,
+ SNDRV_PCM_MMAP_OFFSET_STATUS);
if (pcm->mmap_status == MAP_FAILED)
pcm->mmap_status = NULL;
if (!pcm->mmap_status)
goto mmap_error;
- pcm->mmap_control = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
- MAP_FILE | MAP_SHARED, pcm->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL);
+ pcm->mmap_control = pcm->ops->mmap(pcm->data, NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_FILE | MAP_SHARED, SNDRV_PCM_MMAP_OFFSET_CONTROL);
if (pcm->mmap_control == MAP_FAILED)
pcm->mmap_control = NULL;
if (!pcm->mmap_control) {
- munmap(pcm->mmap_status, page_size);
+ pcm->ops->munmap(pcm->data, pcm->mmap_status, page_size);
pcm->mmap_status = NULL;
goto mmap_error;
}
@@ -395,9 +410,9 @@ static void pcm_hw_munmap_status(struct pcm *pcm) {
} else {
int page_size = sysconf(_SC_PAGE_SIZE);
if (pcm->mmap_status)
- munmap(pcm->mmap_status, page_size);
+ pcm->ops->munmap(pcm->data, pcm->mmap_status, page_size);
if (pcm->mmap_control)
- munmap(pcm->mmap_control, page_size);
+ pcm->ops->munmap(pcm->data, pcm->mmap_control, page_size);
}
pcm->mmap_status = NULL;
pcm->mmap_control = NULL;
@@ -532,12 +547,12 @@ int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
int prepare_error = pcm_prepare(pcm);
if (prepare_error)
return prepare_error;
- if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))
+ if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))
return oops(pcm, errno, "cannot write initial data");
pcm->running = 1;
return 0;
}
- if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {
+ if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {
pcm->prepared = 0;
pcm->running = 0;
if (errno == EPIPE) {
@@ -573,7 +588,7 @@ int pcm_read(struct pcm *pcm, void *data, unsigned int count)
return -errno;
}
}
- if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {
+ if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {
pcm->prepared = 0;
pcm->running = 0;
if (errno == EPIPE) {
@@ -595,15 +610,22 @@ struct pcm_params *pcm_params_get(unsigned int card, unsigned int device,
unsigned int flags)
{
struct snd_pcm_hw_params *params;
- char fn[256];
+ enum snd_node_type pcm_type;
+ struct pcm_ops *ops;
+ void *snd_node, *data;
int fd;
- snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
- flags & PCM_IN ? 'c' : 'p');
+ snd_node = snd_utils_get_dev_node(card, device, NODE_PCM);
+ pcm_type = snd_utils_get_node_type(snd_node);
+ if (pcm_type == SND_NODE_TYPE_PLUGIN)
+ ops = &plug_ops;
+ else
+ ops = &hw_ops;
- fd = open(fn, O_RDWR);
+ fd = ops->open(card, device, flags, &data, snd_node);
if (fd < 0) {
- fprintf(stderr, "cannot open device '%s'\n", fn);
+ fprintf(stderr, "cannot open device %u for card %u\n",
+ device, card);
goto err_open;
}
@@ -612,20 +634,22 @@ 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);
+ snd_utils_put_dev_node(snd_node);
+ ops->close(data);
return (struct pcm_params *)params;
err_hw_refine:
free(params);
err_calloc:
- close(fd);
+ ops->close(data);
err_open:
+ snd_utils_put_dev_node(snd_node);
return NULL;
}
@@ -688,7 +712,7 @@ static int pcm_param_to_alsa(enum pcm_param param)
}
}
-struct pcm_mask *pcm_params_get_mask(struct pcm_params *pcm_params,
+struct pcm_mask *pcm_params_get_mask(const struct pcm_params *pcm_params,
enum pcm_param param)
{
int p;
@@ -705,7 +729,7 @@ struct pcm_mask *pcm_params_get_mask(struct pcm_params *pcm_params,
return (struct pcm_mask *)param_to_mask(params, p);
}
-unsigned int pcm_params_get_min(struct pcm_params *pcm_params,
+unsigned int pcm_params_get_min(const struct pcm_params *pcm_params,
enum pcm_param param)
{
struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
@@ -737,7 +761,7 @@ void pcm_params_set_min(struct pcm_params *pcm_params,
param_set_min(params, p, val);
}
-unsigned int pcm_params_get_max(struct pcm_params *pcm_params,
+unsigned int pcm_params_get_max(const struct pcm_params *pcm_params,
enum pcm_param param)
{
struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
@@ -859,11 +883,14 @@ int pcm_close(struct pcm *pcm)
if (pcm->flags & PCM_MMAP) {
pcm_stop(pcm);
- munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
+ pcm->ops->munmap(pcm->data, pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
}
- if (pcm->fd >= 0)
- close(pcm->fd);
+ if (pcm->data)
+ pcm->ops->close(pcm->data);
+ if (pcm->snd_node)
+ snd_utils_put_dev_node(pcm->snd_node);
+
pcm->prepared = 0;
pcm->running = 0;
pcm->buffer_size = 0;
@@ -879,36 +906,36 @@ struct pcm *pcm_open(unsigned int card, unsigned int device,
struct snd_pcm_info info;
struct snd_pcm_hw_params params;
struct snd_pcm_sw_params sparams;
- char fn[256];
- int rc;
+ int rc, pcm_type;
if (!config) {
return &bad_pcm; /* TODO: could support default config here */
}
pcm = calloc(1, sizeof(struct pcm));
- if (!pcm)
+ if (!pcm) {
+ oops(&bad_pcm, ENOMEM, "can't allocate PCM object");
return &bad_pcm; /* TODO: could support default config here */
+ }
pcm->config = *config;
+ pcm->flags = flags;
- snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
- flags & PCM_IN ? 'c' : 'p');
+ pcm->snd_node = snd_utils_get_dev_node(card, device, NODE_PCM);
+ pcm_type = snd_utils_get_node_type(pcm->snd_node);
+ if (pcm_type == SND_NODE_TYPE_PLUGIN)
+ pcm->ops = &plug_ops;
+ else
+ pcm->ops = &hw_ops;
- pcm->flags = flags;
- pcm->fd = open(fn, O_RDWR|O_NONBLOCK);
+ pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, pcm->snd_node);
if (pcm->fd < 0) {
- oops(pcm, errno, "cannot open device '%s'", fn);
- return pcm;
- }
-
- if (fcntl(pcm->fd, F_SETFL, fcntl(pcm->fd, F_GETFL) &
- ~O_NONBLOCK) < 0) {
- oops(pcm, errno, "failed to reset blocking mode '%s'", fn);
- goto fail_close;
+ oops(&bad_pcm, errno, "cannot open device %u for card %u",
+ device, card);
+ goto fail_open;
}
- if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {
- oops(pcm, errno, "cannot get info");
+ if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_INFO, &info)) {
+ oops(&bad_pcm, errno, "cannot get info");
goto fail_close;
}
pcm->subdevice = info.subdevice;
@@ -930,7 +957,7 @@ struct pcm *pcm_open(unsigned int card, unsigned int device,
if (flags & PCM_NOIRQ) {
if (!(flags & PCM_MMAP)) {
- oops(pcm, EINVAL, "noirq only currently supported with mmap().");
+ oops(&bad_pcm, EINVAL, "noirq only currently supported with mmap().");
goto fail_close;
}
@@ -945,8 +972,8 @@ struct pcm *pcm_open(unsigned int card, unsigned int device,
param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
SNDRV_PCM_ACCESS_RW_INTERLEAVED);
- if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {
- oops(pcm, errno, "cannot set hw params");
+ if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {
+ oops(&bad_pcm, errno, "cannot set hw params");
goto fail_close;
}
@@ -956,10 +983,11 @@ struct pcm *pcm_open(unsigned int card, unsigned int device,
pcm->buffer_size = config->period_count * config->period_size;
if (flags & PCM_MMAP) {
- pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
- PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0);
+ pcm->mmap_buffer = pcm->ops->mmap(pcm->data, NULL,
+ pcm_frames_to_bytes(pcm, pcm->buffer_size),
+ PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, 0);
if (pcm->mmap_buffer == MAP_FAILED) {
- oops(pcm, errno, "failed to mmap buffer %d bytes\n",
+ oops(&bad_pcm, errno, "failed to mmap buffer %d bytes\n",
pcm_frames_to_bytes(pcm, pcm->buffer_size));
goto fail_close;
}
@@ -1006,23 +1034,23 @@ struct pcm *pcm_open(unsigned int card, unsigned int device,
while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size)
pcm->boundary *= 2;
- if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {
- oops(pcm, errno, "cannot set sw params");
+ if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {
+ oops(&bad_pcm, errno, "cannot set sw params");
goto fail;
}
rc = pcm_hw_mmap_status(pcm);
if (rc < 0) {
- oops(pcm, errno, "mmap status failed");
+ oops(&bad_pcm, errno, "mmap status failed");
goto fail;
}
#ifdef SNDRV_PCM_IOCTL_TTSTAMP
if (pcm->flags & PCM_MONOTONIC) {
int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;
- rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg);
+ rc = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_TTSTAMP, &arg);
if (rc < 0) {
- oops(pcm, errno, "cannot set timestamp type");
+ oops(&bad_pcm, errno, "cannot set timestamp type");
goto fail;
}
}
@@ -1033,11 +1061,14 @@ struct pcm *pcm_open(unsigned int card, unsigned int device,
fail:
if (flags & PCM_MMAP)
- munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
+ pcm->ops->munmap(pcm->data, pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
fail_close:
- close(pcm->fd);
- pcm->fd = -1;
- return pcm;
+ pcm->ops->close(pcm->data);
+
+fail_open:
+ snd_utils_put_dev_node(pcm->snd_node);
+ free(pcm);
+ return &bad_pcm;
}
int pcm_is_ready(struct pcm *pcm)
@@ -1050,7 +1081,7 @@ int pcm_prepare(struct pcm *pcm)
if (pcm->prepared)
return 0;
- 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");
pcm->prepared = 1;
@@ -1064,9 +1095,9 @@ int pcm_start(struct pcm *pcm)
return prepare_error;
if (pcm->flags & PCM_MMAP)
- pcm_sync_ptr(pcm, 0);
+ pcm_sync_ptr(pcm, 0);
- 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");
pcm->running = 1;
@@ -1075,7 +1106,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");
pcm->prepared = 0;
@@ -1195,7 +1226,7 @@ int pcm_wait(struct pcm *pcm, int timeout)
do {
/* let's wait for avail or timeout */
- err = poll(&pfd, 1, timeout);
+ err = pcm->ops->poll(pcm->data, &pfd, 1, timeout);
if (err < 0)
return -errno;
@@ -1251,7 +1282,7 @@ int pcm_mmap_transfer(struct pcm *pcm, const void *buffer, unsigned int bytes)
}
/* start the audio if we reach the threshold */
- if (!pcm->running &&
+ if (!pcm->running &&
(pcm->buffer_size - avail) >= pcm->config.start_threshold) {
if (pcm_start(pcm) < 0) {
fprintf(stderr, "start error: hw 0x%x app 0x%x avail 0x%x\n",
@@ -1347,5 +1378,5 @@ int pcm_ioctl(struct pcm *pcm, int request, ...)
arg = va_arg(ap, void *);
va_end(ap);
- return ioctl(pcm->fd, request, arg);
+ return pcm->ops->ioctl(pcm->data, request, arg);
}
diff --git a/pcm_hw.c b/pcm_hw.c
new file mode 100644
index 0000000..f19f07b
--- /dev/null
+++ b/pcm_hw.c
@@ -0,0 +1,146 @@
+/* pcm_hw.c
+**
+** Copyright (c) 2019, The Linux Foundation. All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above
+** copyright notice, this list of conditions and the following
+** disclaimer in the documentation and/or other materials provided
+** with the distribution.
+** * Neither the name of The Linux Foundation nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <poll.h>
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <linux/ioctl.h>
+#include <sound/asound.h>
+#include <tinyalsa/asoundlib.h>
+
+#include "pcm_io.h"
+
+struct pcm_hw_data {
+ unsigned int card;
+ unsigned int device;
+ unsigned int fd;
+ void *snd_node;
+};
+
+static void pcm_hw_close(void *data)
+{
+ struct pcm_hw_data *hw_data = data;
+
+ if (hw_data->fd >= 0)
+ close(hw_data->fd);
+
+ free(hw_data);
+}
+
+static int pcm_hw_ioctl(void *data, unsigned int cmd, ...)
+{
+ struct pcm_hw_data *hw_data = data;
+ va_list ap;
+ void *arg;
+
+ va_start(ap, cmd);
+ arg = va_arg(ap, void *);
+ va_end(ap);
+
+ return ioctl(hw_data->fd, cmd, arg);
+}
+
+static int pcm_hw_poll(void *data __attribute__((unused)),
+ struct pollfd *pfd, nfds_t nfds, int timeout)
+{
+ return poll(pfd, nfds, timeout);
+}
+
+static void* pcm_hw_mmap(void *data, void *addr, size_t length, int prot,
+ int flags, off_t offset)
+{
+ struct pcm_hw_data *hw_data = data;
+
+ return mmap(addr, length, prot, flags, hw_data->fd, offset);
+}
+
+static int pcm_hw_munmap(void *data __attribute__((unused)), void *addr, size_t length)
+{
+ return munmap(addr, length);
+}
+
+static int pcm_hw_open(unsigned int card, unsigned int device,
+ unsigned int flags, void **data,
+ __attribute__((unused)) void *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');
+ fd = open(fn, O_RDWR|O_NONBLOCK);
+ if (fd < 0) {
+ printf("%s: cannot open device '%s'", __func__, fn);
+ return fd;
+ }
+
+ if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK) < 0) {
+ printf("%s: failed to reset blocking mode '%s'",
+ __func__, fn);
+ goto err_close;
+ }
+
+ hw_data->snd_node = node;
+ hw_data->card = card;
+ hw_data->device = device;
+ hw_data->fd = fd;
+
+ *data = hw_data;
+
+ return fd;
+
+err_close:
+ close(fd);
+ free(hw_data);
+ return -ENODEV;
+}
+
+struct pcm_ops hw_ops = {
+ .open = pcm_hw_open,
+ .close = pcm_hw_close,
+ .ioctl = pcm_hw_ioctl,
+ .mmap = pcm_hw_mmap,
+ .munmap = pcm_hw_munmap,
+ .poll = pcm_hw_poll,
+};
diff --git a/pcm_io.h b/pcm_io.h
new file mode 100644
index 0000000..4d9746d
--- /dev/null
+++ b/pcm_io.h
@@ -0,0 +1,47 @@
+/* pcm.h
+**
+** Copyright (c) 2019, The Linux Foundation. All rights reserved.
+**
+** 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 __PCM_H__
+#define __PCM_H__
+
+#include <poll.h>
+
+struct pcm_ops {
+ int (*open) (unsigned int card, unsigned int device,
+ unsigned int flags, void **data, void *node);
+ void (*close) (void *data);
+ int (*ioctl) (void *data, unsigned int cmd, ...);
+ void* (*mmap) (void *data, void *addr, size_t length, int prot, int flags,
+ off_t offset);
+ int (*munmap) (void *data, void *addr, size_t length);
+ int (*poll) (void *data, struct pollfd *pfd, nfds_t nfds, int timeout);
+};
+
+#endif /* end of __PCM_H__ */
diff --git a/pcm_plugin.c b/pcm_plugin.c
new file mode 100644
index 0000000..14c6cdb
--- /dev/null
+++ b/pcm_plugin.c
@@ -0,0 +1,799 @@
+/* pcm_plugin.c
+**
+** Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
+**
+** 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/pcm_plugin.h>
+
+#include "pcm_io.h"
+#include "snd_utils.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;
+ PCM_PLUGIN_OPEN_FN_PTR();
+
+ struct pcm_plugin *plugin;
+ void *dev_node;
+};
+
+static unsigned int my_params[] = {
+ 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;
+
+ plugin->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(my_params); i++)
+ pcm_plug_interval_select(p, my_params[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 = plugin->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 plugin->ops->sw_params(plugin, params);
+}
+
+static int convert_plugin_to_pcm_state(int plugin_state)
+{
+ switch (plugin_state) {
+ case PCM_PLUG_STATE_SETUP:
+ return PCM_STATE_SETUP;
+ case PCM_PLUG_STATE_RUNNING:
+ return PCM_STATE_RUNNING;
+ case PCM_PLUG_STATE_PREPARED:
+ return PCM_STATE_PREPARED;
+ case PCM_PLUG_STATE_OPEN:
+ return PCM_STATE_OPEN;
+ }
+
+ return PCM_STATE_OPEN;
+}
+
+static int pcm_plug_sync_ptr(struct pcm_plug_data *plug_data,
+ struct snd_pcm_sync_ptr *sync_ptr)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+ int ret = -EBADFD;
+
+ if (plugin->state >= PCM_PLUG_STATE_SETUP) {
+ ret = plugin->ops->sync_ptr(plugin, sync_ptr);
+ if (ret == 0)
+ sync_ptr->s.status.state = convert_plugin_to_pcm_state(plugin->state);
+ }
+
+ return ret;
+}
+
+static int pcm_plug_writei_frames(struct pcm_plug_data *plug_data,
+ struct snd_xferi *x)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+
+ if (plugin->state != PCM_PLUG_STATE_PREPARED &&
+ plugin->state != PCM_PLUG_STATE_RUNNING)
+ return -EBADFD;
+
+ return plugin->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 plugin->ops->readi_frames(plugin, x);
+}
+
+static int pcm_plug_ttstamp(struct pcm_plug_data *plug_data,
+ int *tstamp)
+{
+ struct pcm_plugin *plugin = plug_data->plugin;
+
+ if (plugin->state >= PCM_PLUG_STATE_SETUP)
+ return plugin->ops->ttstamp(plugin, tstamp);
+ else
+ return -EBADFD;
+}
+
+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 = plugin->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 = plugin->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 = 0;
+
+ rc = plugin->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 = plugin->ops->ioctl(plugin, cmd, arg);
+ break;
+ }
+
+ return ret;
+}
+
+static int pcm_plug_poll(void *data, struct pollfd *pfd, nfds_t nfds,
+ int timeout)
+{
+ struct pcm_plug_data *plug_data = data;
+ struct pcm_plugin *plugin = plug_data->plugin;
+
+ return plugin->ops->poll(plugin, pfd, nfds, timeout);
+}
+
+static void* pcm_plug_mmap(void *data, void *addr, size_t length, int prot,
+ int flags, off_t offset)
+{
+ struct pcm_plug_data *plug_data = data;
+ struct pcm_plugin *plugin = plug_data->plugin;
+
+ if (plugin->state != PCM_PLUG_STATE_SETUP)
+ return NULL;
+ return plugin->ops->mmap(plugin, addr, length, prot, flags, offset);
+}
+
+static int pcm_plug_munmap(void *data, void *addr, size_t length)
+{
+ struct pcm_plug_data *plug_data = data;
+ struct pcm_plugin *plugin = plug_data->plugin;
+
+ if (plugin->state != PCM_PLUG_STATE_SETUP)
+ return -EBADFD;
+
+ return plugin->ops->munmap(plugin, addr, length);
+}
+
+static int pcm_plug_open(unsigned int card, unsigned int device,
+ unsigned int flags, void **data, void *pcm_node)
+{
+ struct pcm_plug_data *plug_data;
+ void *dl_hdl;
+ int rc = 0;
+ char *so_name, token[80], *name, *open_fn, *token_saveptr;
+
+ 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: %s\n", __func__, so_name, dlerror());
+ goto err_dl_open;
+ } else {
+ fprintf(stderr, "%s: dlopen successful for %s\n", __func__, so_name);
+ }
+
+ dlerror();
+
+ sscanf(so_name, "lib%s", token);
+ token_saveptr = token;
+ name = strtok_r(token, ".", &token_saveptr);
+ if (!name) {
+ fprintf(stderr, "%s: invalid library name\n", __func__);
+ goto err_open_fn;
+ }
+ open_fn = calloc(1, strlen(name) + strlen("_open") + 1);
+ if (!open_fn) {
+ rc = -ENOMEM;
+ goto err_open_fn;
+ }
+
+ strncpy(open_fn, name, strlen(name) + 1);
+ strcat(open_fn, "_open");
+
+ printf("%s - %s\n", __func__, open_fn);
+ plug_data->plugin_open_fn = dlsym(dl_hdl, open_fn);
+ if (!plug_data->plugin_open_fn) {
+ fprintf(stderr, "%s: dlsym to open fn failed, err = '%s'\n",
+ __func__, dlerror());
+ goto err_dlsym;
+ }
+
+ rc = plug_data->plugin_open_fn(&plug_data->plugin,
+ card, device, flags);
+ if (rc) {
+ fprintf(stderr, "%s: failed to open plugin\n", __func__);
+ goto err_dlsym;
+ }
+
+ /* Call snd-card-def to get card and pcm nodes */
+ /* Check how to manage fd for plugin */
+
+ 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;
+
+ free(open_fn);
+ return 0;
+
+err_dlsym:
+ free(open_fn);
+err_open_fn:
+ dlclose(dl_hdl);
+err_get_lib:
+err_dl_open:
+ free(plug_data);
+
+ return rc;
+}
+
+struct pcm_ops plug_ops = {
+ .open = pcm_plug_open,
+ .close = pcm_plug_close,
+ .ioctl = pcm_plug_ioctl,
+ .mmap = pcm_plug_mmap,
+ .munmap = pcm_plug_munmap,
+ .poll = pcm_plug_poll,
+};
diff --git a/snd_utils.c b/snd_utils.c
new file mode 100644
index 0000000..ac3ddf9
--- /dev/null
+++ b/snd_utils.c
@@ -0,0 +1,149 @@
+/* snd_utils.c
+**
+** Copyright (c) 2019, The Linux Foundation. All rights reserved.
+**
+** 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 <errno.h>
+#include "snd_utils.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->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->get_str(node->dev_node, prop, val);
+}
+
+void snd_utils_put_dev_node(struct snd_node *node)
+{
+ if (!node)
+ return;
+
+ if (node->card_node)
+ node->put_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->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->get_card, "snd_card_def_get_card", err);
+ if (err)
+ goto done;
+ SND_DLSYM(dl, node->put_card, "snd_card_def_put_card", err);
+ if (err)
+ goto done;
+ SND_DLSYM(dl, node->get_node, "snd_card_def_get_node", err);
+ if (err)
+ goto done;
+ SND_DLSYM(dl, node->get_int, "snd_card_def_get_int", err);
+ if (err)
+ goto done;
+ SND_DLSYM(dl, node->get_str, "snd_card_def_get_str", err);
+
+done:
+ return err;
+}
+
+struct snd_node *snd_utils_get_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->get_card(card);
+ if (!node->card_node)
+ goto err_resolve_symbols;
+
+ node->dev_node = node->get_node(node->card_node,
+ device, dev_type);
+ if (!node->dev_node)
+ goto err_get_node;
+
+ return node;
+
+err_get_node:
+ node->put_card(node->card_node);
+
+err_resolve_symbols:
+ dlclose(node->dl_hdl);
+
+err_dl_open:
+ free(node);
+ return NULL;
+}
diff --git a/snd_utils.h b/snd_utils.h
new file mode 100644
index 0000000..64bfeb8
--- /dev/null
+++ b/snd_utils.h
@@ -0,0 +1,71 @@
+/* snd_utils.h
+**
+** Copyright (c) 2019, The Linux Foundation. All rights reserved.
+**
+** 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 __SND_CARD_UTILS_H__
+#define __SND_CARD_UTILS_H__
+
+#include <dlfcn.h>
+
+struct snd_node {
+ void *card_node;
+ void *dev_node;
+ void *dl_hdl;
+
+ void* (*get_card) (unsigned int card);
+ void (*put_card) (void *card);
+ void* (*get_node) (void *card, unsigned int id,
+ int type);
+ int (*get_int) (void *node, const char *prop, int *val);
+ int (*get_str) (void *node, const char *prop, char **val);
+};
+
+enum {
+ NODE_PCM,
+ NODE_MIXER,
+};
+
+enum snd_node_type {
+ SND_NODE_TYPE_HW = 0,
+ SND_NODE_TYPE_PLUGIN,
+ SND_NODE_TYPE_INVALID,
+};
+
+struct snd_node *snd_utils_get_dev_node(unsigned int card,
+ unsigned int device, int dev_type);
+
+void snd_utils_put_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 __SND_CARD_UTILS_H__ */