1 /** 2 * \file pcm/pcm_hooks.c 3 * \ingroup PCM_Hook 4 * \brief PCM Hook Interface 5 * \author Abramo Bagnara <abramo (at) alsa-project.org> 6 * \author Jaroslav Kysela <perex (at) perex.cz> 7 * \date 2000-2001 8 */ 9 /* 10 * PCM - Hook functions 11 * Copyright (c) 2001 by Abramo Bagnara <abramo (at) alsa-project.org> 12 * 13 * 14 * This library is free software; you can redistribute it and/or modify 15 * it under the terms of the GNU Lesser General Public License as 16 * published by the Free Software Foundation; either version 2.1 of 17 * the License, or (at your option) any later version. 18 * 19 * This program is distributed in the hope that it will be useful, 20 * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 * GNU Lesser General Public License for more details. 23 * 24 * You should have received a copy of the GNU Lesser General Public 25 * License along with this library; if not, write to the Free Software 26 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 27 * 28 */ 29 30 #include "pcm_local.h" 31 #include "pcm_generic.h" 32 33 #ifndef PIC 34 /* entry for static linking */ 35 const char *_snd_module_pcm_hooks = ""; 36 #endif 37 38 #ifndef DOC_HIDDEN 39 struct _snd_pcm_hook { 40 snd_pcm_t *pcm; 41 snd_pcm_hook_func_t func; 42 void *private_data; 43 struct list_head list; 44 }; 45 46 typedef struct { 47 snd_pcm_generic_t gen; 48 struct list_head hooks[SND_PCM_HOOK_TYPE_LAST + 1]; 49 } snd_pcm_hooks_t; 50 #endif 51 52 static int snd_pcm_hooks_close(snd_pcm_t *pcm) 53 { 54 snd_pcm_hooks_t *h = pcm->private_data; 55 struct list_head *pos, *next; 56 unsigned int k; 57 int res = 0, err; 58 59 list_for_each_safe(pos, next, &h->hooks[SND_PCM_HOOK_TYPE_CLOSE]) { 60 snd_pcm_hook_t *hook = list_entry(pos, snd_pcm_hook_t, list); 61 err = hook->func(hook); 62 if (err < 0) 63 res = err; 64 } 65 for (k = 0; k <= SND_PCM_HOOK_TYPE_LAST; ++k) { 66 struct list_head *hooks = &h->hooks[k]; 67 while (!list_empty(hooks)) { 68 snd_pcm_hook_t *hook; 69 pos = hooks->next; 70 hook = list_entry(pos, snd_pcm_hook_t, list); 71 snd_pcm_hook_remove(hook); 72 } 73 } 74 err = snd_pcm_generic_close(pcm); 75 if (err < 0) 76 res = err; 77 return res; 78 } 79 80 static int snd_pcm_hooks_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) 81 { 82 snd_pcm_hooks_t *h = pcm->private_data; 83 struct list_head *pos, *next; 84 int err = snd_pcm_generic_hw_params(pcm, params); 85 if (err < 0) 86 return err; 87 list_for_each_safe(pos, next, &h->hooks[SND_PCM_HOOK_TYPE_HW_PARAMS]) { 88 snd_pcm_hook_t *hook = list_entry(pos, snd_pcm_hook_t, list); 89 err = hook->func(hook); 90 if (err < 0) 91 return err; 92 } 93 return 0; 94 } 95 96 static int snd_pcm_hooks_hw_free(snd_pcm_t *pcm) 97 { 98 snd_pcm_hooks_t *h = pcm->private_data; 99 struct list_head *pos, *next; 100 int err = snd_pcm_generic_hw_free(pcm); 101 if (err < 0) 102 return err; 103 list_for_each_safe(pos, next, &h->hooks[SND_PCM_HOOK_TYPE_HW_FREE]) { 104 snd_pcm_hook_t *hook = list_entry(pos, snd_pcm_hook_t, list); 105 err = hook->func(hook); 106 if (err < 0) 107 return err; 108 } 109 return 0; 110 } 111 112 static void snd_pcm_hooks_dump(snd_pcm_t *pcm, snd_output_t *out) 113 { 114 snd_pcm_hooks_t *h = pcm->private_data; 115 snd_output_printf(out, "Hooks PCM\n"); 116 if (pcm->setup) { 117 snd_output_printf(out, "Its setup is:\n"); 118 snd_pcm_dump_setup(pcm, out); 119 } 120 snd_output_printf(out, "Slave: "); 121 snd_pcm_dump(h->gen.slave, out); 122 } 123 124 static const snd_pcm_ops_t snd_pcm_hooks_ops = { 125 .close = snd_pcm_hooks_close, 126 .info = snd_pcm_generic_info, 127 .hw_refine = snd_pcm_generic_hw_refine, 128 .hw_params = snd_pcm_hooks_hw_params, 129 .hw_free = snd_pcm_hooks_hw_free, 130 .sw_params = snd_pcm_generic_sw_params, 131 .channel_info = snd_pcm_generic_channel_info, 132 .dump = snd_pcm_hooks_dump, 133 .nonblock = snd_pcm_generic_nonblock, 134 .async = snd_pcm_generic_async, 135 .mmap = snd_pcm_generic_mmap, 136 .munmap = snd_pcm_generic_munmap, 137 }; 138 139 static const snd_pcm_fast_ops_t snd_pcm_hooks_fast_ops = { 140 .status = snd_pcm_generic_status, 141 .state = snd_pcm_generic_state, 142 .hwsync = snd_pcm_generic_hwsync, 143 .delay = snd_pcm_generic_delay, 144 .prepare = snd_pcm_generic_prepare, 145 .reset = snd_pcm_generic_reset, 146 .start = snd_pcm_generic_start, 147 .drop = snd_pcm_generic_drop, 148 .drain = snd_pcm_generic_drain, 149 .pause = snd_pcm_generic_pause, 150 .rewindable = snd_pcm_generic_rewindable, 151 .rewind = snd_pcm_generic_rewind, 152 .forwardable = snd_pcm_generic_forwardable, 153 .forward = snd_pcm_generic_forward, 154 .resume = snd_pcm_generic_resume, 155 .link = snd_pcm_generic_link, 156 .link_slaves = snd_pcm_generic_link_slaves, 157 .unlink = snd_pcm_generic_unlink, 158 .writei = snd_pcm_generic_writei, 159 .writen = snd_pcm_generic_writen, 160 .readi = snd_pcm_generic_readi, 161 .readn = snd_pcm_generic_readn, 162 .avail_update = snd_pcm_generic_avail_update, 163 .mmap_commit = snd_pcm_generic_mmap_commit, 164 .htimestamp = snd_pcm_generic_htimestamp, 165 .poll_descriptors_count = snd_pcm_generic_poll_descriptors_count, 166 .poll_descriptors = snd_pcm_generic_poll_descriptors, 167 .poll_revents = snd_pcm_generic_poll_revents, 168 }; 169 170 /** 171 * \brief Creates a new hooks PCM 172 * \param pcmp Returns created PCM handle 173 * \param name Name of PCM 174 * \param slave Slave PCM 175 * \param close_slave If set, slave PCM handle is closed when hooks PCM is closed 176 * \retval zero on success otherwise a negative error code 177 * \warning Using of this function might be dangerous in the sense 178 * of compatibility reasons. The prototype might be freely 179 * changed in future. 180 */ 181 int snd_pcm_hooks_open(snd_pcm_t **pcmp, const char *name, snd_pcm_t *slave, int close_slave) 182 { 183 snd_pcm_t *pcm; 184 snd_pcm_hooks_t *h; 185 unsigned int k; 186 int err; 187 assert(pcmp && slave); 188 h = calloc(1, sizeof(snd_pcm_hooks_t)); 189 if (!h) 190 return -ENOMEM; 191 h->gen.slave = slave; 192 h->gen.close_slave = close_slave; 193 for (k = 0; k <= SND_PCM_HOOK_TYPE_LAST; ++k) { 194 INIT_LIST_HEAD(&h->hooks[k]); 195 } 196 err = snd_pcm_new(&pcm, SND_PCM_TYPE_HOOKS, name, slave->stream, slave->mode); 197 if (err < 0) { 198 free(h); 199 return err; 200 } 201 pcm->ops = &snd_pcm_hooks_ops; 202 pcm->fast_ops = &snd_pcm_hooks_fast_ops; 203 pcm->private_data = h; 204 pcm->poll_fd = slave->poll_fd; 205 pcm->poll_events = slave->poll_events; 206 pcm->mmap_shadow = 1; 207 pcm->monotonic = slave->monotonic; 208 snd_pcm_link_hw_ptr(pcm, slave); 209 snd_pcm_link_appl_ptr(pcm, slave); 210 *pcmp = pcm; 211 212 return 0; 213 } 214 215 /*! \page pcm_plugins 216 217 \section pcm_plugins_hooks Plugin: hooks 218 219 This plugin is used to call some 'hook' function when this plugin is opened, 220 modified or closed. 221 Typically, it is used to change control values for a certain state 222 specially for the PCM (see the example below). 223 224 \code 225 # Hook arguments definition 226 hook_args.NAME { 227 ... # Arbitrary arguments 228 } 229 230 # PCM hook type 231 pcm_hook_type.NAME { 232 [lib STR] # Library file (default libasound.so) 233 [install STR] # Install function (default _snd_pcm_hook_NAME_install) 234 } 235 236 # PCM hook definition 237 pcm_hook.NAME { 238 type STR # PCM Hook type (see pcm_hook_type) 239 [args STR] # Arguments for install function (see hook_args) 240 # or 241 [args { }] # Arguments for install function 242 } 243 244 # PCM hook plugin 245 pcm.NAME { 246 type hooks # PCM with hooks 247 slave STR # Slave name 248 # or 249 slave { # Slave definition 250 pcm STR # Slave PCM name 251 # or 252 pcm { } # Slave PCM definition 253 } 254 hooks { 255 ID STR # Hook name (see pcm_hook) 256 # or 257 ID { } # Hook definition (see pcm_hook) 258 } 259 } 260 \endcode 261 262 Example: 263 264 \code 265 hooks.0 { 266 type ctl_elems 267 hook_args [ 268 { 269 name "Wave Surround Playback Volume" 270 preserve true 271 lock true 272 optional true 273 value [ 0 0 ] 274 } 275 { 276 name "EMU10K1 PCM Send Volume" 277 index { @func private_pcm_subdevice } 278 lock true 279 value [ 0 0 0 0 0 0 255 0 0 0 0 255 ] 280 } 281 ] 282 } 283 \endcode 284 Here, the controls "Wave Surround Playback Volume" and "EMU10K1 PCM Send Volume" 285 are set to the given values when this pcm is accessed. Since these controls 286 take multi-dimensional values, the <code>value</code> field is written as 287 an array. 288 When <code>preserve</code> is true, the old values are saved and restored 289 when the pcm is closed. The <code>lock</code> means that the control is 290 locked during this pcm is opened, and cannot be changed by others. 291 When <code>optional</code> is set, no error is returned but ignored 292 even if the specified control doesn't exist. 293 294 \subsection pcm_plugins_hooks_funcref Function reference 295 296 <UL> 297 <LI>The function ctl_elems - _snd_pcm_hook_ctl_elems_install() - installs 298 CTL settings described by given configuration. 299 <LI>snd_pcm_hooks_open() 300 <LI>_snd_pcm_hooks_open() 301 </UL> 302 303 */ 304 305 static int snd_pcm_hook_add_conf(snd_pcm_t *pcm, snd_config_t *root, snd_config_t *conf) 306 { 307 int err; 308 char buf[256]; 309 const char *str, *id; 310 const char *lib = NULL, *install = NULL; 311 snd_config_t *type = NULL, *args = NULL; 312 snd_config_iterator_t i, next; 313 int (*install_func)(snd_pcm_t *pcm, snd_config_t *args) = NULL; 314 void *h = NULL; 315 if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND) { 316 SNDERR("Invalid hook definition"); 317 return -EINVAL; 318 } 319 snd_config_for_each(i, next, conf) { 320 snd_config_t *n = snd_config_iterator_entry(i); 321 const char *id; 322 if (snd_config_get_id(n, &id) < 0) 323 continue; 324 if (strcmp(id, "comment") == 0) 325 continue; 326 if (strcmp(id, "type") == 0) { 327 type = n; 328 continue; 329 } 330 if (strcmp(id, "hook_args") == 0) { 331 args = n; 332 continue; 333 } 334 SNDERR("Unknown field %s", id); 335 return -EINVAL; 336 } 337 if (!type) { 338 SNDERR("type is not defined"); 339 return -EINVAL; 340 } 341 err = snd_config_get_id(type, &id); 342 if (err < 0) { 343 SNDERR("unable to get id"); 344 return err; 345 } 346 err = snd_config_get_string(type, &str); 347 if (err < 0) { 348 SNDERR("Invalid type for %s", id); 349 return err; 350 } 351 err = snd_config_search_definition(root, "pcm_hook_type", str, &type); 352 if (err >= 0) { 353 if (snd_config_get_type(type) != SND_CONFIG_TYPE_COMPOUND) { 354 SNDERR("Invalid type for PCM type %s definition", str); 355 goto _err; 356 } 357 snd_config_for_each(i, next, type) { 358 snd_config_t *n = snd_config_iterator_entry(i); 359 const char *id; 360 if (snd_config_get_id(n, &id) < 0) 361 continue; 362 if (strcmp(id, "comment") == 0) 363 continue; 364 if (strcmp(id, "lib") == 0) { 365 err = snd_config_get_string(n, &lib); 366 if (err < 0) { 367 SNDERR("Invalid type for %s", id); 368 goto _err; 369 } 370 continue; 371 } 372 if (strcmp(id, "install") == 0) { 373 err = snd_config_get_string(n, &install); 374 if (err < 0) { 375 SNDERR("Invalid type for %s", id); 376 goto _err; 377 } 378 continue; 379 } 380 SNDERR("Unknown field %s", id); 381 err = -EINVAL; 382 goto _err; 383 } 384 } 385 if (!install) { 386 install = buf; 387 snprintf(buf, sizeof(buf), "_snd_pcm_hook_%s_install", str); 388 } 389 h = snd_dlopen(lib, RTLD_NOW); 390 install_func = h ? snd_dlsym(h, install, SND_DLSYM_VERSION(SND_PCM_DLSYM_VERSION)) : NULL; 391 err = 0; 392 if (!h) { 393 SNDERR("Cannot open shared library %s", 394 lib ? lib : "[builtin]"); 395 err = -ENOENT; 396 } else if (!install_func) { 397 SNDERR("symbol %s is not defined inside %s", install, 398 lib ? lib : "[builtin]"); 399 snd_dlclose(h); 400 err = -ENXIO; 401 } 402 _err: 403 if (type) 404 snd_config_delete(type); 405 if (err >= 0) { 406 if (args && snd_config_get_string(args, &str) >= 0) { 407 err = snd_config_search_definition(root, "hook_args", str, &args); 408 if (err < 0) 409 SNDERR("unknown hook_args %s", str); 410 else 411 err = install_func(pcm, args); 412 snd_config_delete(args); 413 } else 414 err = install_func(pcm, args); 415 snd_dlclose(h); 416 } 417 if (err < 0) 418 return err; 419 return 0; 420 } 421 422 /** 423 * \brief Creates a new hooks PCM 424 * \param pcmp Returns created PCM handle 425 * \param name Name of PCM 426 * \param root Root configuration node 427 * \param conf Configuration node with hooks PCM description 428 * \param stream PCM Stream 429 * \param mode PCM Mode 430 * \retval zero on success otherwise a negative error code 431 * \warning Using of this function might be dangerous in the sense 432 * of compatibility reasons. The prototype might be freely 433 * changed in future. 434 */ 435 int _snd_pcm_hooks_open(snd_pcm_t **pcmp, const char *name, 436 snd_config_t *root, snd_config_t *conf, 437 snd_pcm_stream_t stream, int mode) 438 { 439 snd_config_iterator_t i, next; 440 int err; 441 snd_pcm_t *rpcm = NULL, *spcm; 442 snd_config_t *slave = NULL, *sconf; 443 snd_config_t *hooks = NULL; 444 snd_config_for_each(i, next, conf) { 445 snd_config_t *n = snd_config_iterator_entry(i); 446 const char *id; 447 if (snd_config_get_id(n, &id) < 0) 448 continue; 449 if (snd_pcm_conf_generic_id(id)) 450 continue; 451 if (strcmp(id, "slave") == 0) { 452 slave = n; 453 continue; 454 } 455 if (strcmp(id, "hooks") == 0) { 456 if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) { 457 SNDERR("Invalid type for %s", id); 458 return -EINVAL; 459 } 460 hooks = n; 461 continue; 462 } 463 SNDERR("Unknown field %s", id); 464 return -EINVAL; 465 } 466 if (!slave) { 467 SNDERR("slave is not defined"); 468 return -EINVAL; 469 } 470 err = snd_pcm_slave_conf(root, slave, &sconf, 0); 471 if (err < 0) 472 return err; 473 err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf); 474 snd_config_delete(sconf); 475 if (err < 0) 476 return err; 477 err = snd_pcm_hooks_open(&rpcm, name, spcm, 1); 478 if (err < 0) { 479 snd_pcm_close(spcm); 480 return err; 481 } 482 if (!hooks) 483 goto _done; 484 snd_config_for_each(i, next, hooks) { 485 snd_config_t *n = snd_config_iterator_entry(i); 486 const char *str; 487 if (snd_config_get_string(n, &str) >= 0) { 488 err = snd_config_search_definition(root, "pcm_hook", str, &n); 489 if (err < 0) { 490 SNDERR("unknown pcm_hook %s", str); 491 } else { 492 err = snd_pcm_hook_add_conf(rpcm, root, n); 493 snd_config_delete(n); 494 } 495 } else 496 err = snd_pcm_hook_add_conf(rpcm, root, n); 497 if (err < 0) { 498 snd_pcm_close(rpcm); 499 return err; 500 } 501 } 502 _done: 503 *pcmp = rpcm; 504 return 0; 505 } 506 #ifndef DOC_HIDDEN 507 SND_DLSYM_BUILD_VERSION(_snd_pcm_hooks_open, SND_PCM_DLSYM_VERSION); 508 #endif 509 510 /** 511 * \brief Get PCM handle for a PCM hook 512 * \param hook PCM hook handle 513 * \return PCM handle 514 */ 515 snd_pcm_t *snd_pcm_hook_get_pcm(snd_pcm_hook_t *hook) 516 { 517 assert(hook); 518 return hook->pcm; 519 } 520 521 /** 522 * \brief Get callback function private data for a PCM hook 523 * \param hook PCM hook handle 524 * \return callback function private data 525 */ 526 void *snd_pcm_hook_get_private(snd_pcm_hook_t *hook) 527 { 528 assert(hook); 529 return hook->private_data; 530 } 531 532 /** 533 * \brief Set callback function private data for a PCM hook 534 * \param hook PCM hook handle 535 * \param private_data The private data value 536 */ 537 void snd_pcm_hook_set_private(snd_pcm_hook_t *hook, void *private_data) 538 { 539 assert(hook); 540 hook->private_data = private_data; 541 } 542 543 /** 544 * \brief Add a PCM hook at end of hooks chain 545 * \param hookp Returned PCM hook handle 546 * \param pcm PCM handle 547 * \param type PCM hook type 548 * \param func PCM hook callback function 549 * \param private_data PCM hook private data 550 * \return 0 on success otherwise a negative error code 551 * 552 * Warning: an hook callback function cannot remove an hook of the same type 553 * different from itself 554 */ 555 int snd_pcm_hook_add(snd_pcm_hook_t **hookp, snd_pcm_t *pcm, 556 snd_pcm_hook_type_t type, 557 snd_pcm_hook_func_t func, void *private_data) 558 { 559 snd_pcm_hook_t *h; 560 snd_pcm_hooks_t *hooks; 561 assert(hookp && func); 562 assert(snd_pcm_type(pcm) == SND_PCM_TYPE_HOOKS); 563 h = calloc(1, sizeof(*h)); 564 if (!h) 565 return -ENOMEM; 566 h->pcm = pcm; 567 h->func = func; 568 h->private_data = private_data; 569 hooks = pcm->private_data; 570 list_add_tail(&h->list, &hooks->hooks[type]); 571 *hookp = h; 572 return 0; 573 } 574 575 /** 576 * \brief Remove a PCM hook 577 * \param hook PCM hook handle 578 * \return 0 on success otherwise a negative error code 579 * 580 * Warning: an hook callback cannot remove an hook of the same type 581 * different from itself 582 */ 583 int snd_pcm_hook_remove(snd_pcm_hook_t *hook) 584 { 585 assert(hook); 586 list_del(&hook->list); 587 free(hook); 588 return 0; 589 } 590 591 /* 592 * 593 */ 594 595 static int snd_pcm_hook_ctl_elems_hw_params(snd_pcm_hook_t *hook) 596 { 597 snd_sctl_t *h = snd_pcm_hook_get_private(hook); 598 return snd_sctl_install(h); 599 } 600 601 static int snd_pcm_hook_ctl_elems_hw_free(snd_pcm_hook_t *hook) 602 { 603 snd_sctl_t *h = snd_pcm_hook_get_private(hook); 604 return snd_sctl_remove(h); 605 } 606 607 static int snd_pcm_hook_ctl_elems_close(snd_pcm_hook_t *hook) 608 { 609 snd_sctl_t *h = snd_pcm_hook_get_private(hook); 610 int err = snd_sctl_free(h); 611 snd_pcm_hook_set_private(hook, NULL); 612 return err; 613 } 614 615 /** 616 * \brief Install CTL settings using hardware associated with PCM handle 617 * \param pcm PCM handle 618 * \param conf Configuration node with CTL settings 619 * \return zero on success otherwise a negative error code 620 */ 621 int _snd_pcm_hook_ctl_elems_install(snd_pcm_t *pcm, snd_config_t *conf) 622 { 623 int err; 624 int card; 625 snd_pcm_info_t *info; 626 char ctl_name[16]; 627 snd_ctl_t *ctl; 628 snd_sctl_t *sctl = NULL; 629 snd_config_t *pcm_conf = NULL; 630 snd_pcm_hook_t *h_hw_params = NULL, *h_hw_free = NULL, *h_close = NULL; 631 assert(conf); 632 assert(snd_config_get_type(conf) == SND_CONFIG_TYPE_COMPOUND); 633 snd_pcm_info_alloca(&info); 634 err = snd_pcm_info(pcm, info); 635 if (err < 0) 636 return err; 637 card = snd_pcm_info_get_card(info); 638 if (card < 0) { 639 SNDERR("No card for this PCM"); 640 return -EINVAL; 641 } 642 sprintf(ctl_name, "hw:%d", card); 643 err = snd_ctl_open(&ctl, ctl_name, 0); 644 if (err < 0) { 645 SNDERR("Cannot open CTL %s", ctl_name); 646 return err; 647 } 648 err = snd_config_imake_pointer(&pcm_conf, "pcm_handle", pcm); 649 if (err < 0) 650 goto _err; 651 err = snd_sctl_build(&sctl, ctl, conf, pcm_conf, 0); 652 if (err < 0) 653 goto _err; 654 err = snd_pcm_hook_add(&h_hw_params, pcm, SND_PCM_HOOK_TYPE_HW_PARAMS, 655 snd_pcm_hook_ctl_elems_hw_params, sctl); 656 if (err < 0) 657 goto _err; 658 err = snd_pcm_hook_add(&h_hw_free, pcm, SND_PCM_HOOK_TYPE_HW_FREE, 659 snd_pcm_hook_ctl_elems_hw_free, sctl); 660 if (err < 0) 661 goto _err; 662 err = snd_pcm_hook_add(&h_close, pcm, SND_PCM_HOOK_TYPE_CLOSE, 663 snd_pcm_hook_ctl_elems_close, sctl); 664 if (err < 0) 665 goto _err; 666 snd_config_delete(pcm_conf); 667 return 0; 668 _err: 669 if (h_hw_params) 670 snd_pcm_hook_remove(h_hw_params); 671 if (h_hw_free) 672 snd_pcm_hook_remove(h_hw_free); 673 if (h_close) 674 snd_pcm_hook_remove(h_close); 675 if (sctl) 676 snd_sctl_free(sctl); 677 if (pcm_conf) 678 snd_config_delete(pcm_conf); 679 return err; 680 } 681 #ifndef DOC_HIDDEN 682 SND_DLSYM_BUILD_VERSION(_snd_pcm_hook_ctl_elems_install, SND_PCM_DLSYM_VERSION); 683 #endif 684