diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-07-15 01:35:19 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-07-15 01:35:19 +0000 |
commit | 7f60111300ff18c398d63b472f4462fef65ce629 (patch) | |
tree | c1fdd4e6db06429e7a726e1453292e3179f810ef | |
parent | bb72d52a81b8fa58c43681edd057430887b050a4 (diff) | |
parent | 43bde0116bb22fa68f3a14a18eef9dc332045f4f (diff) | |
download | tinyalsa-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.bp | 25 | ||||
-rw-r--r-- | METADATA | 3 | ||||
-rw-r--r-- | OWNERS | 4 | ||||
-rw-r--r-- | include/tinyalsa/asoundlib.h | 32 | ||||
-rw-r--r-- | include/tinyalsa/mixer_plugin.h | 193 | ||||
-rw-r--r-- | include/tinyalsa/pcm_plugin.h | 104 | ||||
-rw-r--r-- | mixer.c | 484 | ||||
-rw-r--r-- | mixer_hw.c | 122 | ||||
-rw-r--r-- | mixer_io.h | 52 | ||||
-rw-r--r-- | mixer_plugin.c | 505 | ||||
-rw-r--r-- | pcm.c | 159 | ||||
-rw-r--r-- | pcm_hw.c | 146 | ||||
-rw-r--r-- | pcm_io.h | 47 | ||||
-rw-r--r-- | pcm_plugin.c | 799 | ||||
-rw-r--r-- | snd_utils.c | 149 | ||||
-rw-r--r-- | snd_utils.h | 71 |
16 files changed, 2731 insertions, 164 deletions
@@ -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 +} @@ -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__ */ @@ -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; +} @@ -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(¶ms, SNDRV_PCM_HW_PARAM_ACCESS, SNDRV_PCM_ACCESS_RW_INTERLEAVED); - if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) { - oops(pcm, errno, "cannot set hw params"); + if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) { + 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 = ¶ms->intervals[p - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]; + + i->min = v->min; + i->max = v->max; + + if (is_integer) + i->integer = 1; +} + +static int pcm_plug_frames_to_bytes(unsigned int frames, + unsigned int frame_bits) +{ + return (frames * (frame_bits / 8)); +} + +static int pcm_plug_bytes_to_frames(unsigned int size, + unsigned int frame_bits) +{ + return (size * 8) / frame_bits; +} + +static int pcm_plug_get_params(struct pcm_plugin *plugin, + struct snd_pcm_hw_params *params) +{ + struct pcm_plugin_min_max bw, ch, pb, periods; + struct pcm_plugin_min_max val; + struct pcm_plugin_min_max frame_bits, buffer_bytes; + + /* + * populate the struct snd_pcm_hw_params structure + * using the hw_param constraints provided by plugin + * via the plugin->constraints + */ + + /* Set the mask params */ + pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_ACCESS, + plugin->constraints->access); + pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_FORMAT, + plugin->constraints->format); + pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT, + SNDRV_PCM_SUBFORMAT_STD); + + /* Set the standard interval params */ + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + &plugin->constraints->bit_width, 1); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS, + &plugin->constraints->channels, 1); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_RATE, + &plugin->constraints->rate, 1); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + &plugin->constraints->period_bytes, 0); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIODS, + &plugin->constraints->periods, 1); + + /* set the calculated interval params */ + + bw.min = plugin->constraints->bit_width.min; + bw.max = plugin->constraints->bit_width.max; + + ch.min = plugin->constraints->channels.min; + ch.max = plugin->constraints->channels.max; + + pb.min = plugin->constraints->period_bytes.min; + pb.max = plugin->constraints->period_bytes.max; + + periods.min = plugin->constraints->periods.min; + periods.max = plugin->constraints->periods.max; + + /* Calculate and set frame bits */ + frame_bits.min = bw.min * ch.min; + frame_bits.max = bw.max * ch.max; + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS, + &frame_bits, 1); + + + /* Calculate and set period_size in frames */ + val.min = pcm_plug_bytes_to_frames(pb.min, frame_bits.min); + val.max = pcm_plug_bytes_to_frames(pb.max, frame_bits.min); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + &val, 1); + + /* Calculate and set buffer_bytes */ + buffer_bytes.min = pb.min * periods.min; + buffer_bytes.max = pb.max * periods.max; + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + &buffer_bytes, 1); + + /* Calculate and set buffer_size in frames */ + val.min = pcm_plug_bytes_to_frames(buffer_bytes.min, frame_bits.min); + val.max = pcm_plug_bytes_to_frames(buffer_bytes.max, frame_bits.min); + pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + &val, 1); + return 0; +} + +static int pcm_plug_masks_refine(struct snd_pcm_hw_params *p, + struct snd_pcm_hw_params *c) +{ + struct snd_mask *req_mask; + struct snd_mask *con_mask; + unsigned int idx, i, masks; + + masks = SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK; + + for (idx = 0; idx <= masks; idx++) { + + if (!(p->rmask & (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_MASK)))) + continue; + + req_mask = PCM_PARAM_GET_MASK(p, idx); + con_mask = PCM_PARAM_GET_MASK(c, idx); + + /* + * set the changed mask if requested mask value is not the same as + * constrained mask value + */ + if (memcmp(req_mask, con_mask, PCM_MASK_SIZE * sizeof(uint32_t))) + p->cmask |= 1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_MASK); + + /* Actually change the requested mask to constrained mask */ + for (i = 0; i < PCM_MASK_SIZE; i++) + req_mask->bits[i] &= con_mask->bits[i]; + } + + return 0; +} + +static int pcm_plug_interval_refine(struct snd_pcm_hw_params *p, + struct snd_pcm_hw_params *c) +{ + struct snd_interval *ri; + struct snd_interval *ci; + unsigned int idx; + unsigned int intervals; + int changed = 0; + + intervals = SNDRV_PCM_HW_PARAM_LAST_INTERVAL - + SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; + + for (idx = 0; idx <= intervals; idx++) { + ri = &p->intervals[idx]; + ci = &c->intervals[idx]; + + if (!(p->rmask & (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_INTERVAL)) )) + continue; + + if (ri->min < ci->min) { + ri->min = ci->min; + ri->openmin = ci->openmin; + changed = 1; + } else if (ri->min == ci->min && !ri->openmin && ci->openmin) { + ri->openmin = 1; + changed = 1; + } + + if (ri->max > ci->max) { + ri->max = ci->max; + ri->openmax = ci->openmax; + changed = 1; + } else if (ri->max == ci->max && !ri->openmax && ci->openmax) { + ri->openmax = 1; + changed = 1; + }; + + if (!ri->integer && ci->integer) { + ri->integer = 1; + changed = 1; + } + + if (ri->integer) { + if (ri->openmin) { + ri->min++; + ri->openmin = 0; + } + if (ri->openmax) { + ri->max--; + ri->openmax = 0; + } + } else if (!ri->openmin && !ri->openmax && ri->min == ri->max) { + ri->integer = 1; + } + + /* Set the changed mask */ + if (changed) + p->cmask |= (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_INTERVAL)); + } + + return 0; +} + + +static int pcm_plug_hw_params_refine(struct snd_pcm_hw_params *p, + struct snd_pcm_hw_params *c) +{ + int rc; + + rc = pcm_plug_masks_refine(p, c); + if (rc) { + fprintf(stderr, "%s: masks refine failed %d\n", __func__, rc); + return rc; + } + + rc = pcm_plug_interval_refine(p, c); + if (rc) { + fprintf(stderr, "%s: interval refine failed %d\n", __func__, rc); + return rc; + } + + /* clear the requested params */ + p->rmask = 0; + + return rc; +} + +static int __pcm_plug_hrefine(struct pcm_plug_data *plug_data, + struct snd_pcm_hw_params *params) +{ + struct pcm_plugin *plugin = plug_data->plugin; + struct snd_pcm_hw_params plug_params; + int rc; + + memset(&plug_params, 0, sizeof(plug_params)); + rc = pcm_plug_get_params(plugin, &plug_params); + if (rc) { + fprintf(stderr, "%s: pcm_plug_get_params failed %d\n", + __func__, rc); + return -EINVAL; + } + + return pcm_plug_hw_params_refine(params, &plug_params); + +} + +static int pcm_plug_hrefine(struct pcm_plug_data *plug_data, + struct snd_pcm_hw_params *params) +{ + return __pcm_plug_hrefine(plug_data, params); +} + +static int pcm_plug_interval_select(struct snd_pcm_hw_params *p, + unsigned int param, unsigned int select, unsigned int val) +{ + struct snd_interval *i; + + if (param < SNDRV_PCM_HW_PARAM_FIRST_INTERVAL || + param > SNDRV_PCM_HW_PARAM_LAST_INTERVAL) + return -EINVAL; + + i = &p->intervals[param - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]; + + if (!i->min) + return -EINVAL; + + switch (select) { + + case PCM_PLUG_HW_PARAM_SELECT_MIN: + i->max = i->min; + break; + + case PCM_PLUG_HW_PARAM_SELECT_MAX: + i->min = i->max; + break; + + case PCM_PLUG_HW_PARAM_SELECT_VAL: + i->min = i->max = val; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static void pcm_plug_hw_params_set(struct snd_pcm_hw_params *p) +{ + unsigned int i, select; + unsigned int bw, ch, period_sz, periods; + unsigned int val1, val2, offset; + + offset = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; + + /* Select the min values first */ + select = PCM_PLUG_HW_PARAM_SELECT_MIN; + for (i = 0; i < ARRAY_SIZE(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__ */ |