From 7f651b30714a46e5e73f4e4679edd8ba3e7b7468 Mon Sep 17 00:00:00 2001 From: Abramo Bagnara Date: Mon, 4 Jun 2001 18:04:18 +0000 Subject: [PATCH] Added hooked PCM type (one sample hook implementation will follow). Some cleaning. --- doc/asoundrc.doc | 106 ++++++--- include/local.h | 2 + include/pcm.h | 18 ++ src/control/control.c | 2 +- src/pcm/Makefile.am | 5 +- src/pcm/pcm.c | 41 +++- src/pcm/pcm_adpcm.c | 4 +- src/pcm/pcm_alaw.c | 4 +- src/pcm/pcm_copy.c | 4 +- src/pcm/pcm_file.c | 4 +- src/pcm/pcm_hooks.c | 536 ++++++++++++++++++++++++++++++++++++++++++ src/pcm/pcm_hw.c | 4 +- src/pcm/pcm_linear.c | 4 +- src/pcm/pcm_local.h | 3 +- src/pcm/pcm_meter.c | 24 +- src/pcm/pcm_meter.h | 21 -- src/pcm/pcm_mulaw.c | 4 +- src/pcm/pcm_multi.c | 4 +- src/pcm/pcm_null.c | 4 +- src/pcm/pcm_params.c | 6 +- src/pcm/pcm_plug.c | 4 +- src/pcm/pcm_rate.c | 4 +- src/pcm/pcm_route.c | 4 +- src/pcm/pcm_share.c | 4 +- src/pcm/pcm_shm.c | 4 +- src/pcm/pcm_surr.c | 4 +- src/rawmidi/rawmidi.c | 2 +- src/seq/seq.c | 2 +- 28 files changed, 701 insertions(+), 127 deletions(-) create mode 100644 src/pcm/pcm_hooks.c delete mode 100644 src/pcm/pcm_meter.h diff --git a/doc/asoundrc.doc b/doc/asoundrc.doc index 143a9afc..e2f8c293 100644 --- a/doc/asoundrc.doc +++ b/doc/asoundrc.doc @@ -18,6 +18,12 @@ pcm_scope_type.NAME { [open STR] # Open function (default _snd_pcm_scope_NAME_open) } +# PCM scope definition +pcm_scope.NAME { + type STR # Scope type + ... +} + # Slave PCM definition pcm_slave.NAME { pcm STR # PCM name @@ -31,11 +37,26 @@ pcm_slave.NAME { etc. } +# Hook arguments definition +hook_args.NAME { + ... # Arbitrary arguments +} + +# PCM hook definition +pcm_hook.NAME { + [lib STR] # Library file (default libasound.so) + [install STR] # Install function (default _snd_pcm_hook_NAME_install) + [args STR] # Arguments for install function (see hook_args) + # or + [args { }] # Arguments for install function +} + # PCM definition pcm.NAME { type STR # Type [comment ANY] # Saved comments + # PCM types: type hw # Kernel PCM card INT/STR # Card name or number @@ -43,10 +64,24 @@ pcm.NAME { [subdevice] INT # Subdevice number, -1 first available (default -1) + type hooks # PCM with hooks + slave STR # Slave name (see pcm_slave) + # or + slave { # Slave definition + pcm STR # Slave PCM name + # or + pcm { } # Slave PCM definition + } + hooks { + ID STR # Hook name (see pcm_hook) + # or + ID { } # Hook definition (see pcm_hook) + } + type plug # Format adjusted PCM - slave STR # Slave name + slave STR # Slave name (see pcm_slave) # or - slave { # Slave definition or name + slave { # Slave definition pcm STR # Slave PCM name # or pcm { } # Slave PCM definition @@ -59,13 +94,23 @@ pcm.NAME { } + type copy # Copy conversion PCM + slave STR # Slave name (see pcm_slave) + # or + slave { # Slave definition + pcm STR # Slave PCM name + # or + pcm { } # Slave PCM definition + } + + type linear # Linear format conversion PCM type adpcm # IMA-ADPCM format conversion PCM type alaw # A-Law format conversion PCM type mulaw # Mu-Law format conversion PCM - slave STR # Slave name + slave STR # Slave name (see pcm_slave) # or - slave { # Slave definition or name + slave { # Slave definition pcm STR # Slave PCM name # or pcm { } # Slave PCM definition @@ -74,9 +119,9 @@ pcm.NAME { type rate # Rate conversion PCM - slave STR # Slave name + slave STR # Slave name (see pcm_slave) # or - slave { # Slave definition or name + slave { # Slave definition pcm STR # Slave PCM name # or pcm { } # Slave PCM definition @@ -86,9 +131,9 @@ pcm.NAME { type route # Attenuated static route PCM - slave STR # Slave name + slave STR # Slave name (see pcm_slave) # or - slave { # Slave definition or name + slave { # Slave definition pcm STR # Slave PCM name # or pcm { } # Slave PCM definition @@ -105,9 +150,9 @@ pcm.NAME { type multi # Linked PCMs (exclusive access to selected channels) slaves { # Slaves definitions - N STR # Slave name for slave N + ID STR # Slave name for slave N (see pcm_slave) # or - N { # Slave definition for slave N + ID { # Slave definition for slave N pcm STR # Slave PCM name # or pcm { } # Slave PCM definition @@ -123,9 +168,9 @@ pcm.NAME { type file # File plugin - slave STR # Slave name + slave STR # Slave name (see pcm_slave) # or - slave { # Slave definition or name + slave { # Slave definition pcm STR # Slave PCM name # or pcm { } # Slave PCM definition @@ -137,7 +182,7 @@ pcm.NAME { type meter # Meter PCM - slave STR # Slave name + slave STR # Slave name (see pcm_slave) # or slave { # Slave definition or name pcm STR # Slave PCM name @@ -146,17 +191,16 @@ pcm.NAME { } [frequency INT] # Updates per second scopes { # Scopes - N { # Scope definition - type STR # Scope type - [PARAMS] # Scope params - } + ID STR # Scope name (see pcm_scope) + # or + ID { } # Scope definition (see pcm_scope) } type droute # Attenuated dynamic route PCM (NYI) - slave STR # Slave name + slave STR # Slave name (see pcm_slave) # or - slave { # Slave definition or name + slave { # Slave definition pcm STR # Slave PCM name # or pcm { } # Slave PCM definition @@ -165,7 +209,7 @@ pcm.NAME { } ctl STR # Ctl name bindings { # Bindings table - N { # Binding entry + ID { # Binding entry cchannels { # Client channels C INT # Client channel } @@ -179,9 +223,9 @@ pcm.NAME { type loopback # Loopback server (NYI) server STR # Server name - slave STR # Slave name + slave STR # Slave name (see pcm_slave) # or - slave { # Slave definition or name + slave { # Slave definition pcm STR # Slave PCM name # or pcm { } # Slave PCM definition @@ -198,14 +242,14 @@ pcm.NAME { type share # Share PCM - slave STR # Slave name + slave STR # Slave name (see pcm_slave) bindings { # Bindings table N INT # Slave channel for client channel N } type mix # Mix PCM - slave STR # Slave name + slave STR # Slave name (see pcm_slave) bindings { # Bindings table N INT # Slave channel for client channel N } @@ -317,19 +361,7 @@ pcm.m { } } -scope_type.level { +pcm_scope_type.level { lib /home/abramo/scopes/liblevel.so } -Special PCM names: -hw:CARD,DEV,SUBDEV -hw:CARD,DEV -plug:CARD,DEV,SUBDEV -plug:CARD,DEV -plug:SLAVE_PCM -shm:SOCKET,PCM -file:FNAME,FMT,SLAVE_PCM -file:FNAME,FMT -file:FNAME -null - diff --git a/include/local.h b/include/local.h index 3bf29f93..ceb613dd 100644 --- a/include/local.h +++ b/include/local.h @@ -22,6 +22,8 @@ #ifndef __LOCAL_H #define __LOCAL_H +#define ALSA_LIB "libasound.so" + #ifndef DATADIR #define DATADIR "/usr/share" #endif diff --git a/include/pcm.h b/include/pcm.h index 47a5fa73..2f537f22 100644 --- a/include/pcm.h +++ b/include/pcm.h @@ -235,6 +235,8 @@ typedef struct _snd_pcm snd_pcm_t; enum _snd_pcm_type { /** Kernel level PCM */ SND_PCM_TYPE_HW, + /** Hooked PCM */ + SND_PCM_TYPE_HOOKS, /** One ore more linked PCM with exclusive access to selected channels */ SND_PCM_TYPE_MULTI, @@ -718,6 +720,22 @@ void snd_pcm_info_set_device(snd_pcm_info_t *obj, unsigned int val); void snd_pcm_info_set_subdevice(snd_pcm_info_t *obj, unsigned int val); void snd_pcm_info_set_stream(snd_pcm_info_t *obj, snd_pcm_stream_t val); +typedef enum _snd_pcm_hook_type { + SND_PCM_HOOK_HW_PARAMS, + SND_PCM_HOOK_HW_FREE, + SND_PCM_HOOK_CLOSE, + SND_PCM_HOOK_LAST = SND_PCM_HOOK_CLOSE, +} snd_pcm_hook_type_t; + +typedef struct _snd_pcm_hook snd_pcm_hook_t; +typedef int (*snd_pcm_hook_func_t)(snd_pcm_hook_t *hook); +snd_pcm_t *snd_pcm_hook_get_pcm(snd_pcm_hook_t *hook); +void *snd_pcm_hook_get_private(snd_pcm_hook_t *hook); +int snd_pcm_hook_add(snd_pcm_hook_t **hookp, snd_pcm_t *pcm, + snd_pcm_hook_type_t type, + snd_pcm_hook_func_t func, void *private_data); +int snd_pcm_hook_remove(snd_pcm_hook_t *hook); + #ifdef __cplusplus } #endif diff --git a/src/control/control.c b/src/control/control.c index 937ae28a..90482765 100644 --- a/src/control/control.c +++ b/src/control/control.c @@ -444,7 +444,7 @@ int snd_ctl_open_conf(snd_ctl_t **ctlp, const char *name, snprintf(buf, sizeof(buf), "_snd_ctl_%s_open", str); } if (!lib) - lib = "libasound.so"; + lib = ALSA_LIB; h = dlopen(lib, RTLD_NOW); if (!h) { SNDERR("Cannot open shared library %s", lib); diff --git a/src/pcm/Makefile.am b/src/pcm/Makefile.am index b257aaf7..cec1f315 100644 --- a/src/pcm/Makefile.am +++ b/src/pcm/Makefile.am @@ -2,11 +2,12 @@ EXTRA_LTLIBRARIES = libpcm.la libpcm_la_SOURCES = atomic.c mask.c interval.c \ - pcm.c pcm_hw.c pcm_plugin.c pcm_copy.c pcm_linear.c \ + pcm.c pcm_params.c \ + pcm_hw.c pcm_plugin.c pcm_copy.c pcm_linear.c \ pcm_route.c pcm_mulaw.c pcm_alaw.c pcm_adpcm.c \ pcm_rate.c pcm_plug.c pcm_misc.c pcm_mmap.c pcm_multi.c \ pcm_shm.c pcm_file.c pcm_null.c pcm_share.c \ - pcm_meter.c pcm_params.c pcm_surr.c + pcm_meter.c pcm_hooks.c pcm_surr.c noinst_HEADERS = atomic.h pcm_local.h pcm_plugin.h mask.h mask_inline.h \ interval.h interval_inline.h plugin_ops.h diff --git a/src/pcm/pcm.c b/src/pcm/pcm.c index 6095bd65..44050d91 100644 --- a/src/pcm/pcm.c +++ b/src/pcm/pcm.c @@ -96,7 +96,6 @@ snd_pcm_stream_t snd_pcm_stream(snd_pcm_t *pcm) */ int snd_pcm_close(snd_pcm_t *pcm) { - int ret = 0; int err; assert(pcm); if (pcm->setup) { @@ -107,11 +106,11 @@ int snd_pcm_close(snd_pcm_t *pcm) snd_pcm_drain(pcm); err = snd_pcm_hw_free(pcm); if (err < 0) - ret = err; + return err; } - if ((err = pcm->ops->close(pcm->op_arg)) < 0) - ret = err; - pcm->setup = 0; + err = pcm->ops->close(pcm->op_arg); + if (err < 0) + return err; if (pcm->name) free(pcm->name); free(pcm); @@ -190,8 +189,9 @@ int snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) int err; assert(pcm && params); err = _snd_pcm_hw_params(pcm, params); - if (err >= 0) - err = snd_pcm_prepare(pcm); + if (err < 0) + return err; + err = snd_pcm_prepare(pcm); return err; } @@ -211,7 +211,9 @@ int snd_pcm_hw_free(snd_pcm_t *pcm) } err = pcm->ops->hw_free(pcm->op_arg); pcm->setup = 0; - return err; + if (err < 0) + return err; + return 0; } /** \brief Install PCM software configuration defined by params @@ -975,7 +977,7 @@ static int snd_pcm_open_conf(snd_pcm_t **pcmp, const char *name, snprintf(buf, sizeof(buf), "_snd_pcm_%s_open", str); } if (!lib) - lib = "libasound.so"; + lib = ALSA_LIB; h = dlopen(lib, RTLD_NOW); if (!h) { SNDERR("Cannot open shared library %s", lib); @@ -987,7 +989,10 @@ static int snd_pcm_open_conf(snd_pcm_t **pcmp, const char *name, dlclose(h); return -ENXIO; } - return open_func(pcmp, name, pcm_conf, stream, mode); + err = open_func(pcmp, name, pcm_conf, stream, mode); + if (err < 0) + return err; + return 0; } static int snd_pcm_open_noupdate(snd_pcm_t **pcmp, const char *name, @@ -4283,6 +4288,10 @@ int snd_pcm_slave_conf(snd_config_t *conf, snd_config_t **pcm_conf, return err; } } + if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND) { + SNDERR("Invalid slave definition"); + return -EINVAL; + } va_start(args, count); for (k = 0; k < count; ++k) { fields[k].index = va_arg(args, int); @@ -4354,4 +4363,16 @@ int snd_pcm_slave_conf(snd_config_t *conf, snd_config_t **pcm_conf, return 0; } + +int snd_pcm_conf_generic_id(const char *id) +{ + static const char *ids[] = { "comment", "type" }; + unsigned int k; + for (k = 0; k < sizeof(ids) / sizeof(ids[0]); ++k) { + if (strcmp(id, ids[k]) == 0) + return 1; + } + return 0; +} + #endif diff --git a/src/pcm/pcm_adpcm.c b/src/pcm/pcm_adpcm.c index 69e25095..405e4d47 100644 --- a/src/pcm/pcm_adpcm.c +++ b/src/pcm/pcm_adpcm.c @@ -559,9 +559,7 @@ int _snd_pcm_adpcm_open(snd_pcm_t **pcmp, const char *name, snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = snd_config_get_id(n); - if (strcmp(id, "comment") == 0) - continue; - if (strcmp(id, "type") == 0) + if (snd_pcm_conf_generic_id(id)) continue; if (strcmp(id, "slave") == 0) { slave = n; diff --git a/src/pcm/pcm_alaw.c b/src/pcm/pcm_alaw.c index e2d57cfe..71e0d2fa 100644 --- a/src/pcm/pcm_alaw.c +++ b/src/pcm/pcm_alaw.c @@ -432,9 +432,7 @@ int _snd_pcm_alaw_open(snd_pcm_t **pcmp, const char *name, snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = snd_config_get_id(n); - if (strcmp(id, "comment") == 0) - continue; - if (strcmp(id, "type") == 0) + if (snd_pcm_conf_generic_id(id)) continue; if (strcmp(id, "slave") == 0) { slave = n; diff --git a/src/pcm/pcm_copy.c b/src/pcm/pcm_copy.c index a9f85b88..a4bbd254 100644 --- a/src/pcm/pcm_copy.c +++ b/src/pcm/pcm_copy.c @@ -201,9 +201,7 @@ int _snd_pcm_copy_open(snd_pcm_t **pcmp, const char *name, snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = snd_config_get_id(n); - if (strcmp(id, "comment") == 0) - continue; - if (strcmp(id, "type") == 0) + if (snd_pcm_conf_generic_id(id)) continue; if (strcmp(id, "slave") == 0) { slave = n; diff --git a/src/pcm/pcm_file.c b/src/pcm/pcm_file.c index 9e378e0e..d49c2a1e 100644 --- a/src/pcm/pcm_file.c +++ b/src/pcm/pcm_file.c @@ -466,9 +466,7 @@ int _snd_pcm_file_open(snd_pcm_t **pcmp, const char *name, snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = snd_config_get_id(n); - if (strcmp(id, "comment") == 0) - continue; - if (strcmp(id, "type") == 0) + if (snd_pcm_conf_generic_id(id)) continue; if (strcmp(id, "slave") == 0) { slave = n; diff --git a/src/pcm/pcm_hooks.c b/src/pcm/pcm_hooks.c new file mode 100644 index 00000000..0fd19651 --- /dev/null +++ b/src/pcm/pcm_hooks.c @@ -0,0 +1,536 @@ +/* + * PCM - Hook functions + * Copyright (c) 2001 by Abramo Bagnara + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include "pcm_local.h" + +#ifndef DOC_HIDDEN +struct _snd_pcm_hook { + snd_pcm_t *pcm; + snd_pcm_hook_func_t func; + void *private_data; + struct list_head list; +}; + +typedef struct { + snd_pcm_t *slave; + int close_slave; + struct list_head hooks[SND_PCM_HOOK_LAST + 1]; +} snd_pcm_hooks_t; + +static int snd_pcm_hooks_close(snd_pcm_t *pcm) +{ + snd_pcm_hooks_t *h = pcm->private_data; + struct list_head *pos, *next; + unsigned int k; + int err; + if (h->close_slave) { + err = snd_pcm_close(h->slave); + if (err < 0) + return err; + } + list_for_each_safe(pos, next, &h->hooks[SND_PCM_HOOK_CLOSE]) { + snd_pcm_hook_t *hook = list_entry(pos, snd_pcm_hook_t, list); + err = hook->func(hook); + if (err < 0) + return err; + } + for (k = 0; k <= SND_PCM_HOOK_LAST; ++k) { + struct list_head *hooks = &h->hooks[k]; + while (!list_empty(hooks)) { + snd_pcm_hook_t *hook; + pos = hooks->next; + hook = list_entry(pos, snd_pcm_hook_t, list); + snd_pcm_hook_remove(hook); + } + } + free(h); + return 0; +} + +static int snd_pcm_hooks_nonblock(snd_pcm_t *pcm, int nonblock) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_nonblock(h->slave, nonblock); +} + +static int snd_pcm_hooks_async(snd_pcm_t *pcm, int sig, pid_t pid) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_async(h->slave, sig, pid); +} + +static int snd_pcm_hooks_info(snd_pcm_t *pcm, snd_pcm_info_t *info) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_info(h->slave, info); +} + +static int snd_pcm_hooks_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t * info) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_channel_info(h->slave, info); +} + +static int snd_pcm_hooks_status(snd_pcm_t *pcm, snd_pcm_status_t * status) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_status(h->slave, status); +} + +static snd_pcm_state_t snd_pcm_hooks_state(snd_pcm_t *pcm) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_state(h->slave); +} + +static int snd_pcm_hooks_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_delay(h->slave, delayp); +} + +static int snd_pcm_hooks_prepare(snd_pcm_t *pcm) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_prepare(h->slave); +} + +static int snd_pcm_hooks_reset(snd_pcm_t *pcm) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_reset(h->slave); +} + +static int snd_pcm_hooks_start(snd_pcm_t *pcm) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_start(h->slave); +} + +static int snd_pcm_hooks_drop(snd_pcm_t *pcm) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_drop(h->slave); +} + +static int snd_pcm_hooks_drain(snd_pcm_t *pcm) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_drain(h->slave); +} + +static int snd_pcm_hooks_pause(snd_pcm_t *pcm, int enable) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_pause(h->slave, enable); +} + +static snd_pcm_sframes_t snd_pcm_hooks_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_rewind(h->slave, frames); +} + +static snd_pcm_sframes_t snd_pcm_hooks_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_writei(h->slave, buffer, size); +} + +static snd_pcm_sframes_t snd_pcm_hooks_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_writen(h->slave, bufs, size); +} + +static snd_pcm_sframes_t snd_pcm_hooks_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_readi(h->slave, buffer, size); +} + +static snd_pcm_sframes_t snd_pcm_hooks_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_readn(h->slave, bufs, size); +} + +static snd_pcm_sframes_t snd_pcm_hooks_mmap_commit(snd_pcm_t *pcm, + snd_pcm_uframes_t offset, + snd_pcm_uframes_t size) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_mmap_commit(h->slave, offset, size); +} + +static snd_pcm_sframes_t snd_pcm_hooks_avail_update(snd_pcm_t *pcm) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_avail_update(h->slave); +} + +static int snd_pcm_hooks_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_hw_refine(h->slave, params); +} + +static int snd_pcm_hooks_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) +{ + snd_pcm_hooks_t *h = pcm->private_data; + struct list_head *pos, *next; + int err = snd_pcm_hw_params(h->slave, params); + if (err < 0) + return err; + list_for_each_safe(pos, next, &h->hooks[SND_PCM_HOOK_HW_PARAMS]) { + snd_pcm_hook_t *hook = list_entry(pos, snd_pcm_hook_t, list); + err = hook->func(hook); + if (err < 0) + return err; + } + return 0; +} + +static int snd_pcm_hooks_hw_free(snd_pcm_t *pcm) +{ + snd_pcm_hooks_t *h = pcm->private_data; + struct list_head *pos, *next; + int err = snd_pcm_hw_free(h->slave); + if (err < 0) + return err; + list_for_each_safe(pos, next, &h->hooks[SND_PCM_HOOK_HW_FREE]) { + snd_pcm_hook_t *hook = list_entry(pos, snd_pcm_hook_t, list); + err = hook->func(hook); + if (err < 0) + return err; + } + return 0; +} + +static int snd_pcm_hooks_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params) +{ + snd_pcm_hooks_t *h = pcm->private_data; + return snd_pcm_sw_params(h->slave, params); +} + +static int snd_pcm_hooks_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + return 0; +} + +static int snd_pcm_hooks_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + return 0; +} + +static void snd_pcm_hooks_dump(snd_pcm_t *pcm, snd_output_t *out) +{ + snd_pcm_hooks_t *h = pcm->private_data; + snd_output_printf(out, "Hooks PCM\n"); + if (pcm->setup) { + snd_output_printf(out, "Its setup is:\n"); + snd_pcm_dump_setup(pcm, out); + } + snd_output_printf(out, "Slave: "); + snd_pcm_dump(h->slave, out); +} + +snd_pcm_ops_t snd_pcm_hooks_ops = { + close: snd_pcm_hooks_close, + info: snd_pcm_hooks_info, + hw_refine: snd_pcm_hooks_hw_refine, + hw_params: snd_pcm_hooks_hw_params, + hw_free: snd_pcm_hooks_hw_free, + sw_params: snd_pcm_hooks_sw_params, + channel_info: snd_pcm_hooks_channel_info, + dump: snd_pcm_hooks_dump, + nonblock: snd_pcm_hooks_nonblock, + async: snd_pcm_hooks_async, + mmap: snd_pcm_hooks_mmap, + munmap: snd_pcm_hooks_munmap, +}; + +snd_pcm_fast_ops_t snd_pcm_hooks_fast_ops = { + status: snd_pcm_hooks_status, + state: snd_pcm_hooks_state, + delay: snd_pcm_hooks_delay, + prepare: snd_pcm_hooks_prepare, + reset: snd_pcm_hooks_reset, + start: snd_pcm_hooks_start, + drop: snd_pcm_hooks_drop, + drain: snd_pcm_hooks_drain, + pause: snd_pcm_hooks_pause, + rewind: snd_pcm_hooks_rewind, + writei: snd_pcm_hooks_writei, + writen: snd_pcm_hooks_writen, + readi: snd_pcm_hooks_readi, + readn: snd_pcm_hooks_readn, + avail_update: snd_pcm_hooks_avail_update, + mmap_commit: snd_pcm_hooks_mmap_commit, +}; + +int snd_pcm_hooks_open(snd_pcm_t **pcmp, const char *name, snd_pcm_t *slave, int close_slave) +{ + snd_pcm_t *pcm; + snd_pcm_hooks_t *h; + unsigned int k; + assert(pcmp && slave); + h = calloc(1, sizeof(snd_pcm_hooks_t)); + if (!h) + return -ENOMEM; + h->slave = slave; + h->close_slave = close_slave; + for (k = 0; k <= SND_PCM_HOOK_LAST; ++k) { + INIT_LIST_HEAD(&h->hooks[k]); + } + pcm = calloc(1, sizeof(snd_pcm_t)); + if (!pcm) { + free(h); + return -ENOMEM; + } + if (name) + pcm->name = strdup(name); + pcm->type = SND_PCM_TYPE_HOOKS; + pcm->stream = slave->stream; + pcm->mode = slave->mode; + pcm->ops = &snd_pcm_hooks_ops; + pcm->op_arg = pcm; + pcm->fast_ops = &snd_pcm_hooks_fast_ops; + pcm->fast_op_arg = pcm; + pcm->private_data = h; + pcm->poll_fd = slave->poll_fd; + pcm->hw_ptr = slave->hw_ptr; + pcm->appl_ptr = slave->appl_ptr; + *pcmp = pcm; + + return 0; +} + +int snd_pcm_hook_add_conf(snd_pcm_t *pcm, snd_config_t *conf) +{ + int err; + char buf[256]; + const char *str; + const char *lib = NULL, *install = NULL; + snd_config_t *args = NULL; + snd_config_iterator_t i, next; + int (*install_func)(snd_pcm_t *pcm, snd_config_t *args); + void *h; + if (snd_config_get_string(conf, &str) >= 0) { + err = snd_config_search_alias(snd_config, "pcm_hook", str, &conf); + if (err < 0) { + SNDERR("unknown pcm_hook %s", str); + return err; + } + } + if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND) { + SNDERR("Invalid hook definition"); + return -EINVAL; + } + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id = snd_config_get_id(n); + if (strcmp(id, "comment") == 0) + continue; + if (strcmp(id, "lib") == 0) { + err = snd_config_get_string(n, &lib); + if (err < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + continue; + } + if (strcmp(id, "install") == 0) { + err = snd_config_get_string(n, &install); + if (err < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + continue; + } + if (strcmp(id, "args") == 0) { + args = n; + continue; + } + SNDERR("Unknown field %s", id); + return -EINVAL; + } + if (args && snd_config_get_string(args, &str) >= 0) { + err = snd_config_search_alias(snd_config, "hook_args", str, &args); + if (err < 0) { + SNDERR("unknown hook_args %s", str); + return err; + } + } + if (!install) { + install = buf; + snprintf(buf, sizeof(buf), "_snd_pcm_hook_%s_install", + snd_config_get_id(conf)); + } + if (!lib) + lib = ALSA_LIB; + h = dlopen(lib, RTLD_NOW); + if (!h) { + SNDERR("Cannot open shared library %s", lib); + return -ENOENT; + } + install_func = dlsym(h, install); + if (!install_func) { + SNDERR("symbol %s is not defined inside %s", install, lib); + dlclose(h); + return -ENXIO; + } + err = install_func(pcm, args); + if (err < 0) + return err; + return 0; +} + +int _snd_pcm_hooks_open(snd_pcm_t **pcmp, const char *name, + snd_config_t *conf, + snd_pcm_stream_t stream, int mode) +{ + snd_config_iterator_t i, next; + int err; + snd_pcm_t *spcm; + snd_config_t *slave = NULL, *sconf; + snd_config_t *hooks = NULL; + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id = snd_config_get_id(n); + if (snd_pcm_conf_generic_id(id)) + continue; + if (strcmp(id, "slave") == 0) { + slave = n; + continue; + } + if (strcmp(id, "hooks") == 0) { + if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + hooks = n; + continue; + } + SNDERR("Unknown field %s", id); + return -EINVAL; + } + if (!slave) { + SNDERR("slave is not defined"); + return -EINVAL; + } + err = snd_pcm_slave_conf(slave, &sconf, 0); + if (err < 0) + return err; + err = snd_pcm_open_slave(&spcm, sconf, stream, mode); + if (err < 0) + return err; + err = snd_pcm_hooks_open(pcmp, name, spcm, 1); + if (err < 0) { + snd_pcm_close(spcm); + return err; + } + if (!hooks) + return 0; + snd_config_for_each(i, next, hooks) { + snd_config_t *n = snd_config_iterator_entry(i); + err = snd_pcm_hook_add_conf(*pcmp, n); + if (err < 0) { + snd_pcm_close(*pcmp); + return err; + } + } + return 0; +} + +#endif + +/** + * \brief Get PCM handle for a PCM hook + * \param hook PCM hook handle + * \return PCM handle + */ +snd_pcm_t *snd_pcm_hook_get_pcm(snd_pcm_hook_t *hook) +{ + assert(hook); + return hook->pcm; +} + +/** + * \brief Get callback function private data for a PCM hook + * \param hook PCM hook handle + * \return callback function private data + */ +void *snd_pcm_hook_get_private(snd_pcm_hook_t *hook) +{ + assert(hook); + return hook->private_data; +} + +/** + * \brief Add a PCM hook at end of hooks chain + * \param hookp Returned PCM hook handle + * \param pcm PCM handle + * \param type PCM hook type + * \param func PCM hook callback function + * \param private_data PCM hook private data + * \return 0 on success otherwise a negative error code + * + * Warning: an hook callback function cannot remove an hook of the same type + * different from itself + */ +int snd_pcm_hook_add(snd_pcm_hook_t **hookp, snd_pcm_t *pcm, + snd_pcm_hook_type_t type, + snd_pcm_hook_func_t func, void *private_data) +{ + snd_pcm_hook_t *h; + snd_pcm_hooks_t *hooks; + assert(hookp && func); + assert(snd_pcm_type(pcm) == SND_PCM_TYPE_HOOKS); + h = calloc(1, sizeof(*h)); + if (!h) + return -ENOMEM; + h->pcm = pcm; + h->func = func; + h->private_data = private_data; + hooks = pcm->private_data; + list_add_tail(&h->list, &hooks->hooks[type]); + *hookp = h; + return 0; +} + +/** + * \brief Remove a PCM hook + * \param hook PCM hook handle + * \return 0 on success otherwise a negative error code + * + * Warning: an hook callback cannot remove an hook of the same type + * different from itself + */ +int snd_pcm_hook_remove(snd_pcm_hook_t *hook) +{ + assert(hook); + list_del(&hook->list); + free(hook); + return 0; +} + diff --git a/src/pcm/pcm_hw.c b/src/pcm/pcm_hw.c index 1c1e51a9..f90fd4de 100644 --- a/src/pcm/pcm_hw.c +++ b/src/pcm/pcm_hw.c @@ -669,9 +669,7 @@ int _snd_pcm_hw_open(snd_pcm_t **pcmp, const char *name, snd_config_t *conf, snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = snd_config_get_id(n); - if (strcmp(id, "comment") == 0) - continue; - if (strcmp(id, "type") == 0) + if (snd_pcm_conf_generic_id(id)) continue; if (strcmp(id, "card") == 0) { err = snd_config_get_integer(n, &card); diff --git a/src/pcm/pcm_linear.c b/src/pcm/pcm_linear.c index d4f9cb75..1384cf79 100644 --- a/src/pcm/pcm_linear.c +++ b/src/pcm/pcm_linear.c @@ -337,9 +337,7 @@ int _snd_pcm_linear_open(snd_pcm_t **pcmp, const char *name, snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = snd_config_get_id(n); - if (strcmp(id, "comment") == 0) - continue; - if (strcmp(id, "type") == 0) + if (snd_pcm_conf_generic_id(id)) continue; if (strcmp(id, "slave") == 0) { slave = n; diff --git a/src/pcm/pcm_local.h b/src/pcm/pcm_local.h index af827976..a890708e 100644 --- a/src/pcm/pcm_local.h +++ b/src/pcm/pcm_local.h @@ -31,6 +31,7 @@ #define _snd_pcm_subformat_mask _snd_mask #include "local.h" +#include "list.h" #define SND_INTERVAL_INLINE #include "interval.h" @@ -169,7 +170,6 @@ struct _snd_pcm { snd_pcm_channel_info_t *mmap_channels; snd_pcm_channel_area_t *running_areas; snd_pcm_channel_area_t *stopped_areas; - void *stopped; snd_pcm_ops_t *ops; snd_pcm_fast_ops_t *fast_ops; snd_pcm_t *op_arg; @@ -532,6 +532,7 @@ int snd_pcm_slave_conf(snd_config_t *conf, snd_config_t **pcm_conf, int snd_pcm_open_slave(snd_pcm_t **pcmp, snd_config_t *conf, snd_pcm_stream_t stream, int mode); +int snd_pcm_conf_generic_id(const char *id); #define SND_PCM_HW_PARBIT_ACCESS (1U << SND_PCM_HW_PARAM_ACCESS) #define SND_PCM_HW_PARBIT_FORMAT (1U << SND_PCM_HW_PARAM_FORMAT) diff --git a/src/pcm/pcm_meter.c b/src/pcm/pcm_meter.c index 28bbe667..77259ba5 100644 --- a/src/pcm/pcm_meter.c +++ b/src/pcm/pcm_meter.c @@ -653,6 +653,18 @@ static int snd_pcm_meter_add_scope_conf(snd_pcm_t *pcm, const char *name, snd_config_t *); void *h; int err; + err = snd_config_get_string(conf, &str); + if (err >= 0) { + err = snd_config_search_alias(snd_config, "pcm_scope", str, &conf); + if (err < 0) { + SNDERR("unknown pcm_scope %s", str); + return err; + } + } + if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND) { + SNDERR("Invalid type for scope %s", str); + return -EINVAL; + } err = snd_config_search(conf, "type", &c); if (err < 0) { SNDERR("type is not defined"); @@ -695,7 +707,7 @@ static int snd_pcm_meter_add_scope_conf(snd_pcm_t *pcm, const char *name, snprintf(buf, sizeof(buf), "_snd_pcm_scope_%s_open", str); } if (!lib) - lib = "libasound.so"; + lib = ALSA_LIB; h = dlopen(lib, RTLD_NOW); if (!h) { SNDERR("Cannot open shared library %s", lib); @@ -724,9 +736,7 @@ int _snd_pcm_meter_open(snd_pcm_t **pcmp, const char *name, snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = snd_config_get_id(n); - if (strcmp(id, "comment") == 0) - continue; - if (strcmp(id, "type") == 0) + if (snd_pcm_conf_generic_id(id)) continue; if (strcmp(id, "slave") == 0) { slave = n; @@ -762,8 +772,10 @@ int _snd_pcm_meter_open(snd_pcm_t **pcmp, const char *name, if (err < 0) return err; err = snd_pcm_meter_open(pcmp, name, frequency > 0 ? (unsigned int) frequency : FREQUENCY, spcm, 1); - if (err < 0) + if (err < 0) { snd_pcm_close(spcm); + return err; + } if (!scopes) return 0; snd_config_for_each(i, next, scopes) { @@ -772,7 +784,7 @@ int _snd_pcm_meter_open(snd_pcm_t **pcmp, const char *name, err = snd_pcm_meter_add_scope_conf(*pcmp, id, n); if (err < 0) { snd_pcm_close(*pcmp); - return -EINVAL; + return err; } } return 0; diff --git a/src/pcm/pcm_meter.h b/src/pcm/pcm_meter.h deleted file mode 100644 index aef020fd..00000000 --- a/src/pcm/pcm_meter.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * PCM - Meter plugin - * Copyright (c) 2001 by Abramo Bagnara - * - * This library is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - - diff --git a/src/pcm/pcm_mulaw.c b/src/pcm/pcm_mulaw.c index 9f307937..7d921e30 100644 --- a/src/pcm/pcm_mulaw.c +++ b/src/pcm/pcm_mulaw.c @@ -447,9 +447,7 @@ int _snd_pcm_mulaw_open(snd_pcm_t **pcmp, const char *name, snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = snd_config_get_id(n); - if (strcmp(id, "comment") == 0) - continue; - if (strcmp(id, "type") == 0) + if (snd_pcm_conf_generic_id(id)) continue; if (strcmp(id, "slave") == 0) { slave = n; diff --git a/src/pcm/pcm_multi.c b/src/pcm/pcm_multi.c index 65e0d454..e8e7359e 100644 --- a/src/pcm/pcm_multi.c +++ b/src/pcm/pcm_multi.c @@ -665,9 +665,7 @@ int _snd_pcm_multi_open(snd_pcm_t **pcmp, const char *name, snd_config_t *conf, snd_config_for_each(i, inext, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = snd_config_get_id(n); - if (strcmp(id, "comment") == 0) - continue; - if (strcmp(id, "type") == 0) + if (snd_pcm_conf_generic_id(id)) continue; if (strcmp(id, "slaves") == 0) { if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) { diff --git a/src/pcm/pcm_null.c b/src/pcm/pcm_null.c index 387ad4cd..402ff9a7 100644 --- a/src/pcm/pcm_null.c +++ b/src/pcm/pcm_null.c @@ -372,9 +372,7 @@ int _snd_pcm_null_open(snd_pcm_t **pcmp, const char *name, snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = snd_config_get_id(n); - if (strcmp(id, "comment") == 0) - continue; - if (strcmp(id, "type") == 0) + if (snd_pcm_conf_generic_id(id)) continue; SNDERR("Unknown field %s", id); return -EINVAL; diff --git a/src/pcm/pcm_params.c b/src/pcm/pcm_params.c index ac7099c9..380ef054 100644 --- a/src/pcm/pcm_params.c +++ b/src/pcm/pcm_params.c @@ -2067,7 +2067,7 @@ int _snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) if (pcm->setup) { err = snd_pcm_hw_free(pcm); if (err < 0) - return 0; + return err; } err = pcm->ops->hw_params(pcm->op_arg, params); if (err < 0) @@ -2110,6 +2110,8 @@ int _snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) pcm->access == SND_PCM_ACCESS_MMAP_COMPLEX) { err = snd_pcm_mmap(pcm); } - return err; + if (err < 0) + return err; + return 0; } diff --git a/src/pcm/pcm_plug.c b/src/pcm/pcm_plug.c index 3c18d719..3a6f6150 100644 --- a/src/pcm/pcm_plug.c +++ b/src/pcm/pcm_plug.c @@ -724,9 +724,7 @@ int _snd_pcm_plug_open(snd_pcm_t **pcmp, const char *name, snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = snd_config_get_id(n); - if (strcmp(id, "comment") == 0) - continue; - if (strcmp(id, "type") == 0) + if (snd_pcm_conf_generic_id(id)) continue; if (strcmp(id, "slave") == 0) { slave = n; diff --git a/src/pcm/pcm_rate.c b/src/pcm/pcm_rate.c index 236532d8..743c3679 100644 --- a/src/pcm/pcm_rate.c +++ b/src/pcm/pcm_rate.c @@ -547,9 +547,7 @@ int _snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = snd_config_get_id(n); - if (strcmp(id, "comment") == 0) - continue; - if (strcmp(id, "type") == 0) + if (snd_pcm_conf_generic_id(id)) continue; if (strcmp(id, "slave") == 0) { slave = n; diff --git a/src/pcm/pcm_route.c b/src/pcm/pcm_route.c index 95299f55..b4c6efc8 100644 --- a/src/pcm/pcm_route.c +++ b/src/pcm/pcm_route.c @@ -854,9 +854,7 @@ int _snd_pcm_route_open(snd_pcm_t **pcmp, const char *name, snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = snd_config_get_id(n); - if (strcmp(id, "comment") == 0) - continue; - if (strcmp(id, "type") == 0) + if (snd_pcm_conf_generic_id(id)) continue; if (strcmp(id, "slave") == 0) { slave = n; diff --git a/src/pcm/pcm_share.c b/src/pcm/pcm_share.c index c1ca99b2..ad133f38 100644 --- a/src/pcm/pcm_share.c +++ b/src/pcm/pcm_share.c @@ -1387,9 +1387,7 @@ int _snd_pcm_share_open(snd_pcm_t **pcmp, const char *name, snd_config_t *conf, snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = snd_config_get_id(n); - if (strcmp(id, "comment") == 0) - continue; - if (strcmp(id, "type") == 0) + if (snd_pcm_conf_generic_id(id)) continue; if (strcmp(id, "slave") == 0) { err = snd_config_get_string(n, &slave_name); diff --git a/src/pcm/pcm_shm.c b/src/pcm/pcm_shm.c index 4b87ed22..4173b83d 100644 --- a/src/pcm/pcm_shm.c +++ b/src/pcm/pcm_shm.c @@ -735,9 +735,7 @@ int _snd_pcm_shm_open(snd_pcm_t **pcmp, const char *name, snd_config_t *conf, snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = snd_config_get_id(n); - if (strcmp(id, "comment") == 0) - continue; - if (strcmp(id, "type") == 0) + if (snd_pcm_conf_generic_id(id)) continue; if (strcmp(id, "server") == 0) { err = snd_config_get_string(n, &server); diff --git a/src/pcm/pcm_surr.c b/src/pcm/pcm_surr.c index ec94b16d..b8b5bc2c 100644 --- a/src/pcm/pcm_surr.c +++ b/src/pcm/pcm_surr.c @@ -1035,9 +1035,7 @@ int _snd_pcm_surround_open(snd_pcm_t **pcmp, const char *name, snd_config_t *con snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id = snd_config_get_id(n); - if (strcmp(id, "comment") == 0) - continue; - if (strcmp(id, "type") == 0) + if (snd_pcm_conf_generic_id(id)) continue; if (strcmp(id, "card") == 0) { err = snd_config_get_integer(n, &card); diff --git a/src/rawmidi/rawmidi.c b/src/rawmidi/rawmidi.c index 5603176e..4741750c 100644 --- a/src/rawmidi/rawmidi.c +++ b/src/rawmidi/rawmidi.c @@ -127,7 +127,7 @@ int snd_rawmidi_open_conf(snd_rawmidi_t **inputp, snd_rawmidi_t **outputp, snprintf(buf, sizeof(buf), "_snd_rawmidi_%s_open", str); } if (!lib) - lib = "libasound.so"; + lib = ALSA_LIB; h = dlopen(lib, RTLD_NOW); if (!h) { SNDERR("Cannot open shared library %s", lib); diff --git a/src/seq/seq.c b/src/seq/seq.c index 8de8449f..c0179259 100644 --- a/src/seq/seq.c +++ b/src/seq/seq.c @@ -102,7 +102,7 @@ static int snd_seq_open_conf(snd_seq_t **seqp, const char *name, snprintf(buf, sizeof(buf), "_snd_seq_%s_open", str); } if (!lib) - lib = "libasound.so"; + lib = ALSA_LIB; h = dlopen(lib, RTLD_NOW); if (!h) { SNDERR("Cannot open shared library %s", lib); -- 2.47.1