1/** 2 * \file pcm/pcm_plugin.c 3 * \ingroup PCM 4 * \brief PCM Interface 5 * \author Jaroslav Kysela <perex@perex.cz> 6 * \author Abramo Bagnara <abramo@alsa-project.org> 7 * \date 2000-2001 8 */ 9/* 10 * PCM - Common plugin code 11 * Copyright (c) 2000 by Abramo Bagnara <abramo@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/*! 31 32\page pcm_plugins PCM (digital audio) plugins 33 34PCM plugins extends functionality and features of PCM devices. 35The plugins take care about various sample conversions, sample 36copying among channels and so on. 37 38\section pcm_plugins_slave Slave definition 39 40The slave plugin can be specified directly with a string or the definition 41can be entered inside a compound configuration node. Some restrictions can 42be also specified (like static rate or count of channels). 43 44\code 45pcm_slave.NAME { 46 pcm STR # PCM name 47 # or 48 pcm { } # PCM definition 49 format STR # Format or "unchanged" 50 channels INT # Count of channels or "unchanged" string 51 rate INT # Rate in Hz or "unchanged" string 52 period_time INT # Period time in us or "unchanged" string 53 buffer_time INT # Buffer time in us or "unchanged" string 54} 55\endcode 56 57Example: 58 59\code 60pcm_slave.slave_rate44100Hz { 61 pcm "hw:0,0" 62 rate 44100 63} 64 65pcm.rate44100Hz { 66 type plug 67 slave slave_rate44100Hz 68} 69\endcode 70 71The equivalent configuration (in one compound): 72 73\code 74pcm.rate44100Hz { 75 type plug 76 slave { 77 pcm "hw:0,0" 78 rate 44100 79 } 80} 81\endcode 82 83*/ 84 85#include <sys/shm.h> 86#include <limits.h> 87#include "pcm_local.h" 88#include "pcm_plugin.h" 89 90#ifndef DOC_HIDDEN 91 92static snd_pcm_sframes_t 93snd_pcm_plugin_undo_read(snd_pcm_t *pcm ATTRIBUTE_UNUSED, 94 const snd_pcm_channel_area_t *res_areas ATTRIBUTE_UNUSED, 95 snd_pcm_uframes_t res_offset ATTRIBUTE_UNUSED, 96 snd_pcm_uframes_t res_size ATTRIBUTE_UNUSED, 97 snd_pcm_uframes_t slave_undo_size ATTRIBUTE_UNUSED) 98{ 99 return -EIO; 100} 101 102static snd_pcm_sframes_t 103snd_pcm_plugin_undo_write(snd_pcm_t *pcm ATTRIBUTE_UNUSED, 104 const snd_pcm_channel_area_t *res_areas ATTRIBUTE_UNUSED, 105 snd_pcm_uframes_t res_offset ATTRIBUTE_UNUSED, 106 snd_pcm_uframes_t res_size ATTRIBUTE_UNUSED, 107 snd_pcm_uframes_t slave_undo_size ATTRIBUTE_UNUSED) 108{ 109 return -EIO; 110} 111 112snd_pcm_sframes_t 113snd_pcm_plugin_undo_read_generic(snd_pcm_t *pcm ATTRIBUTE_UNUSED, 114 const snd_pcm_channel_area_t *res_areas ATTRIBUTE_UNUSED, 115 snd_pcm_uframes_t res_offset ATTRIBUTE_UNUSED, 116 snd_pcm_uframes_t res_size ATTRIBUTE_UNUSED, 117 snd_pcm_uframes_t slave_undo_size) 118{ 119 return slave_undo_size; 120} 121 122snd_pcm_sframes_t 123snd_pcm_plugin_undo_write_generic(snd_pcm_t *pcm ATTRIBUTE_UNUSED, 124 const snd_pcm_channel_area_t *res_areas ATTRIBUTE_UNUSED, 125 snd_pcm_uframes_t res_offset ATTRIBUTE_UNUSED, 126 snd_pcm_uframes_t res_size ATTRIBUTE_UNUSED, 127 snd_pcm_uframes_t slave_undo_size) 128{ 129 return slave_undo_size; 130} 131 132void snd_pcm_plugin_init(snd_pcm_plugin_t *plugin) 133{ 134 memset(plugin, 0, sizeof(snd_pcm_plugin_t)); 135 plugin->undo_read = snd_pcm_plugin_undo_read; 136 plugin->undo_write = snd_pcm_plugin_undo_write; 137 snd_atomic_write_init(&plugin->watom); 138} 139 140static int snd_pcm_plugin_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) 141{ 142 snd_pcm_plugin_t *plugin = pcm->private_data; 143 snd_pcm_sframes_t sd; 144 int err = snd_pcm_delay(plugin->gen.slave, &sd); 145 if (err < 0) 146 return err; 147 if (pcm->stream == SND_PCM_STREAM_CAPTURE && 148 pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED && 149 pcm->access != SND_PCM_ACCESS_RW_NONINTERLEAVED) { 150 sd += snd_pcm_mmap_capture_avail(pcm); 151 } 152 153 *delayp = sd; 154 return 0; 155} 156 157static int snd_pcm_plugin_prepare(snd_pcm_t *pcm) 158{ 159 snd_pcm_plugin_t *plugin = pcm->private_data; 160 int err; 161 snd_atomic_write_begin(&plugin->watom); 162 err = snd_pcm_prepare(plugin->gen.slave); 163 if (err < 0) { 164 snd_atomic_write_end(&plugin->watom); 165 return err; 166 } 167 *pcm->hw.ptr = 0; 168 *pcm->appl.ptr = 0; 169 snd_atomic_write_end(&plugin->watom); 170 if (plugin->init) { 171 err = plugin->init(pcm); 172 if (err < 0) 173 return err; 174 } 175 return 0; 176} 177 178static int snd_pcm_plugin_reset(snd_pcm_t *pcm) 179{ 180 snd_pcm_plugin_t *plugin = pcm->private_data; 181 int err; 182 snd_atomic_write_begin(&plugin->watom); 183 err = snd_pcm_reset(plugin->gen.slave); 184 if (err < 0) { 185 snd_atomic_write_end(&plugin->watom); 186 return err; 187 } 188 *pcm->hw.ptr = 0; 189 *pcm->appl.ptr = 0; 190 snd_atomic_write_end(&plugin->watom); 191 if (plugin->init) { 192 err = plugin->init(pcm); 193 if (err < 0) 194 return err; 195 } 196 return 0; 197} 198 199static snd_pcm_sframes_t snd_pcm_plugin_rewindable(snd_pcm_t *pcm) 200{ 201 return snd_pcm_mmap_hw_avail(pcm); 202} 203 204static snd_pcm_sframes_t snd_pcm_plugin_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames) 205{ 206 snd_pcm_plugin_t *plugin = pcm->private_data; 207 snd_pcm_sframes_t n = snd_pcm_mmap_hw_avail(pcm); 208 snd_pcm_sframes_t sframes; 209 210 if ((snd_pcm_uframes_t)n < frames) 211 frames = n; 212 if (frames == 0) 213 return 0; 214 215 sframes = frames; 216 snd_atomic_write_begin(&plugin->watom); 217 sframes = snd_pcm_rewind(plugin->gen.slave, sframes); 218 if (sframes < 0) { 219 snd_atomic_write_end(&plugin->watom); 220 return sframes; 221 } 222 snd_pcm_mmap_appl_backward(pcm, (snd_pcm_uframes_t) frames); 223 snd_atomic_write_end(&plugin->watom); 224 return (snd_pcm_sframes_t) frames; 225} 226 227static snd_pcm_sframes_t snd_pcm_plugin_forwardable(snd_pcm_t *pcm) 228{ 229 return snd_pcm_mmap_avail(pcm); 230} 231 232static snd_pcm_sframes_t snd_pcm_plugin_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) 233{ 234 snd_pcm_plugin_t *plugin = pcm->private_data; 235 snd_pcm_sframes_t n = snd_pcm_mmap_avail(pcm); 236 snd_pcm_sframes_t sframes; 237 238 if ((snd_pcm_uframes_t)n < frames) 239 frames = n; 240 if (frames == 0) 241 return 0; 242 243 sframes = frames; 244 snd_atomic_write_begin(&plugin->watom); 245 sframes = INTERNAL(snd_pcm_forward)(plugin->gen.slave, sframes); 246 if (sframes < 0) { 247 snd_atomic_write_end(&plugin->watom); 248 return sframes; 249 } 250 snd_pcm_mmap_appl_forward(pcm, (snd_pcm_uframes_t) frames); 251 snd_atomic_write_end(&plugin->watom); 252 return (snd_pcm_sframes_t) frames; 253} 254 255static snd_pcm_sframes_t snd_pcm_plugin_write_areas(snd_pcm_t *pcm, 256 const snd_pcm_channel_area_t *areas, 257 snd_pcm_uframes_t offset, 258 snd_pcm_uframes_t size) 259{ 260 snd_pcm_plugin_t *plugin = pcm->private_data; 261 snd_pcm_t *slave = plugin->gen.slave; 262 snd_pcm_uframes_t xfer = 0; 263 snd_pcm_sframes_t result; 264 int err; 265 266 while (size > 0) { 267 snd_pcm_uframes_t frames = size; 268 const snd_pcm_channel_area_t *slave_areas; 269 snd_pcm_uframes_t slave_offset; 270 snd_pcm_uframes_t slave_frames = ULONG_MAX; 271 272 err = snd_pcm_mmap_begin(slave, &slave_areas, &slave_offset, &slave_frames); 273 if (err < 0 || slave_frames == 0) 274 break; 275 frames = plugin->write(pcm, areas, offset, frames, 276 slave_areas, slave_offset, &slave_frames); 277 if (CHECK_SANITY(slave_frames > snd_pcm_mmap_playback_avail(slave))) { 278 SNDMSG("write overflow %ld > %ld", slave_frames, 279 snd_pcm_mmap_playback_avail(slave)); 280 return -EPIPE; 281 } 282 snd_atomic_write_begin(&plugin->watom); 283 snd_pcm_mmap_appl_forward(pcm, frames); 284 result = snd_pcm_mmap_commit(slave, slave_offset, slave_frames); 285 if (result > 0 && (snd_pcm_uframes_t)result != slave_frames) { 286 snd_pcm_sframes_t res; 287 res = plugin->undo_write(pcm, slave_areas, slave_offset + result, slave_frames, slave_frames - result); 288 if (res < 0) 289 return xfer > 0 ? (snd_pcm_sframes_t)xfer : res; 290 frames -= res; 291 } 292 snd_atomic_write_end(&plugin->watom); 293 if (result <= 0) 294 return xfer > 0 ? (snd_pcm_sframes_t)xfer : result; 295 offset += frames; 296 xfer += frames; 297 size -= frames; 298 } 299 return (snd_pcm_sframes_t)xfer; 300} 301 302static snd_pcm_sframes_t snd_pcm_plugin_read_areas(snd_pcm_t *pcm, 303 const snd_pcm_channel_area_t *areas, 304 snd_pcm_uframes_t offset, 305 snd_pcm_uframes_t size) 306{ 307 snd_pcm_plugin_t *plugin = pcm->private_data; 308 snd_pcm_t *slave = plugin->gen.slave; 309 snd_pcm_uframes_t xfer = 0; 310 snd_pcm_sframes_t result; 311 312 while (size > 0) { 313 snd_pcm_uframes_t frames = size; 314 const snd_pcm_channel_area_t *slave_areas; 315 snd_pcm_uframes_t slave_offset; 316 snd_pcm_uframes_t slave_frames = ULONG_MAX; 317 318 snd_pcm_mmap_begin(slave, &slave_areas, &slave_offset, &slave_frames); 319 if (slave_frames == 0) 320 break; 321 frames = (plugin->read)(pcm, areas, offset, frames, 322 slave_areas, slave_offset, &slave_frames); 323 if (CHECK_SANITY(slave_frames > snd_pcm_mmap_capture_avail(slave))) { 324 SNDMSG("read overflow %ld > %ld", slave_frames, 325 snd_pcm_mmap_playback_avail(slave)); 326 return -EPIPE; 327 } 328 snd_atomic_write_begin(&plugin->watom); 329 snd_pcm_mmap_appl_forward(pcm, frames); 330 result = snd_pcm_mmap_commit(slave, slave_offset, slave_frames); 331 if (result > 0 && (snd_pcm_uframes_t)result != slave_frames) { 332 snd_pcm_sframes_t res; 333 334 res = plugin->undo_read(slave, areas, offset, frames, slave_frames - result); 335 if (res < 0) 336 return xfer > 0 ? (snd_pcm_sframes_t)xfer : res; 337 frames -= res; 338 } 339 snd_atomic_write_end(&plugin->watom); 340 if (result <= 0) 341 return xfer > 0 ? (snd_pcm_sframes_t)xfer : result; 342 offset += frames; 343 xfer += frames; 344 size -= frames; 345 } 346 return (snd_pcm_sframes_t)xfer; 347} 348 349 350static snd_pcm_sframes_t 351snd_pcm_plugin_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size) 352{ 353 snd_pcm_channel_area_t areas[pcm->channels]; 354 snd_pcm_areas_from_buf(pcm, areas, (void*)buffer); 355 return snd_pcm_write_areas(pcm, areas, 0, size, 356 snd_pcm_plugin_write_areas); 357} 358 359static snd_pcm_sframes_t 360snd_pcm_plugin_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size) 361{ 362 snd_pcm_channel_area_t areas[pcm->channels]; 363 snd_pcm_areas_from_bufs(pcm, areas, bufs); 364 return snd_pcm_write_areas(pcm, areas, 0, size, 365 snd_pcm_plugin_write_areas); 366} 367 368static snd_pcm_sframes_t 369snd_pcm_plugin_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size) 370{ 371 snd_pcm_channel_area_t areas[pcm->channels]; 372 snd_pcm_areas_from_buf(pcm, areas, buffer); 373 return snd_pcm_read_areas(pcm, areas, 0, size, 374 snd_pcm_plugin_read_areas); 375} 376 377static snd_pcm_sframes_t 378snd_pcm_plugin_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size) 379{ 380 snd_pcm_channel_area_t areas[pcm->channels]; 381 snd_pcm_areas_from_bufs(pcm, areas, bufs); 382 return snd_pcm_read_areas(pcm, areas, 0, size, 383 snd_pcm_plugin_read_areas); 384} 385 386static snd_pcm_sframes_t 387snd_pcm_plugin_mmap_commit(snd_pcm_t *pcm, 388 snd_pcm_uframes_t offset ATTRIBUTE_UNUSED, 389 snd_pcm_uframes_t size) 390{ 391 snd_pcm_plugin_t *plugin = pcm->private_data; 392 snd_pcm_t *slave = plugin->gen.slave; 393 const snd_pcm_channel_area_t *areas; 394 snd_pcm_uframes_t appl_offset; 395 snd_pcm_sframes_t slave_size; 396 snd_pcm_sframes_t xfer; 397 398 if (pcm->stream == SND_PCM_STREAM_CAPTURE) { 399 snd_atomic_write_begin(&plugin->watom); 400 snd_pcm_mmap_appl_forward(pcm, size); 401 snd_atomic_write_end(&plugin->watom); 402 return size; 403 } 404 slave_size = snd_pcm_avail_update(slave); 405 if (slave_size < 0) 406 return slave_size; 407 areas = snd_pcm_mmap_areas(pcm); 408 appl_offset = snd_pcm_mmap_offset(pcm); 409 xfer = 0; 410 while (size > 0 && slave_size > 0) { 411 snd_pcm_uframes_t frames = size; 412 snd_pcm_uframes_t cont = pcm->buffer_size - appl_offset; 413 const snd_pcm_channel_area_t *slave_areas; 414 snd_pcm_uframes_t slave_offset; 415 snd_pcm_uframes_t slave_frames = ULONG_MAX; 416 snd_pcm_sframes_t result; 417 int err; 418 419 err = snd_pcm_mmap_begin(slave, &slave_areas, &slave_offset, &slave_frames); 420 if (err < 0) 421 return xfer > 0 ? xfer : err; 422 if (frames > cont) 423 frames = cont; 424 frames = plugin->write(pcm, areas, appl_offset, frames, 425 slave_areas, slave_offset, &slave_frames); 426 snd_atomic_write_begin(&plugin->watom); 427 snd_pcm_mmap_appl_forward(pcm, frames); 428 result = snd_pcm_mmap_commit(slave, slave_offset, slave_frames); 429 snd_atomic_write_end(&plugin->watom); 430 if (result > 0 && (snd_pcm_uframes_t)result != slave_frames) { 431 snd_pcm_sframes_t res; 432 433 res = plugin->undo_write(pcm, slave_areas, slave_offset + result, slave_frames, slave_frames - result); 434 if (res < 0) 435 return xfer > 0 ? xfer : res; 436 frames -= res; 437 } 438 if (result <= 0) 439 return xfer > 0 ? xfer : result; 440 if (frames == cont) 441 appl_offset = 0; 442 else 443 appl_offset += result; 444 size -= frames; 445 slave_size -= frames; 446 xfer += frames; 447 } 448 if (CHECK_SANITY(size)) { 449 SNDMSG("short commit: %ld", size); 450 return -EPIPE; 451 } 452 return xfer; 453} 454 455static snd_pcm_sframes_t snd_pcm_plugin_avail_update(snd_pcm_t *pcm) 456{ 457 snd_pcm_plugin_t *plugin = pcm->private_data; 458 snd_pcm_t *slave = plugin->gen.slave; 459 snd_pcm_sframes_t slave_size; 460 461 slave_size = snd_pcm_avail_update(slave); 462 if (pcm->stream == SND_PCM_STREAM_CAPTURE && 463 pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED && 464 pcm->access != SND_PCM_ACCESS_RW_NONINTERLEAVED) 465 goto _capture; 466 *pcm->hw.ptr = *slave->hw.ptr; 467 return slave_size; 468 _capture: 469 { 470 const snd_pcm_channel_area_t *areas; 471 snd_pcm_uframes_t xfer, hw_offset, size; 472 473 xfer = snd_pcm_mmap_capture_avail(pcm); 474 size = pcm->buffer_size - xfer; 475 areas = snd_pcm_mmap_areas(pcm); 476 hw_offset = snd_pcm_mmap_hw_offset(pcm); 477 while (size > 0 && slave_size > 0) { 478 snd_pcm_uframes_t frames = size; 479 snd_pcm_uframes_t cont = pcm->buffer_size - hw_offset; 480 const snd_pcm_channel_area_t *slave_areas; 481 snd_pcm_uframes_t slave_offset; 482 snd_pcm_uframes_t slave_frames = ULONG_MAX; 483 snd_pcm_sframes_t result; 484 int err; 485 486 err = snd_pcm_mmap_begin(slave, &slave_areas, &slave_offset, &slave_frames); 487 if (err < 0) 488 return xfer > 0 ? (snd_pcm_sframes_t)xfer : err; 489 if (frames > cont) 490 frames = cont; 491 frames = (plugin->read)(pcm, areas, hw_offset, frames, 492 slave_areas, slave_offset, &slave_frames); 493 snd_atomic_write_begin(&plugin->watom); 494 snd_pcm_mmap_hw_forward(pcm, frames); 495 result = snd_pcm_mmap_commit(slave, slave_offset, slave_frames); 496 snd_atomic_write_end(&plugin->watom); 497 if (result > 0 && (snd_pcm_uframes_t)result != slave_frames) { 498 snd_pcm_sframes_t res; 499 500 res = plugin->undo_read(slave, areas, hw_offset, frames, slave_frames - result); 501 if (res < 0) 502 return xfer > 0 ? (snd_pcm_sframes_t)xfer : res; 503 frames -= res; 504 } 505 if (result <= 0) 506 return xfer > 0 ? (snd_pcm_sframes_t)xfer : result; 507 if (frames == cont) 508 hw_offset = 0; 509 else 510 hw_offset += frames; 511 size -= frames; 512 slave_size -= slave_frames; 513 xfer += frames; 514 } 515 return (snd_pcm_sframes_t)xfer; 516 } 517} 518 519static int snd_pcm_plugin_status(snd_pcm_t *pcm, snd_pcm_status_t * status) 520{ 521 snd_pcm_plugin_t *plugin = pcm->private_data; 522 snd_pcm_sframes_t err; 523 snd_atomic_read_t ratom; 524 snd_atomic_read_init(&ratom, &plugin->watom); 525 _again: 526 snd_atomic_read_begin(&ratom); 527 /* sync with the latest hw and appl ptrs */ 528 snd_pcm_plugin_avail_update(pcm); 529 530 err = snd_pcm_status(plugin->gen.slave, status); 531 if (err < 0) { 532 snd_atomic_read_ok(&ratom); 533 return err; 534 } 535 status->appl_ptr = *pcm->appl.ptr; 536 status->hw_ptr = *pcm->hw.ptr; 537 if (!snd_atomic_read_ok(&ratom)) { 538 snd_atomic_read_wait(&ratom); 539 goto _again; 540 } 541 return 0; 542} 543 544const snd_pcm_fast_ops_t snd_pcm_plugin_fast_ops = { 545 .status = snd_pcm_plugin_status, 546 .state = snd_pcm_generic_state, 547 .hwsync = snd_pcm_generic_hwsync, 548 .delay = snd_pcm_plugin_delay, 549 .prepare = snd_pcm_plugin_prepare, 550 .reset = snd_pcm_plugin_reset, 551 .start = snd_pcm_generic_start, 552 .drop = snd_pcm_generic_drop, 553 .drain = snd_pcm_generic_drain, 554 .pause = snd_pcm_generic_pause, 555 .rewindable = snd_pcm_plugin_rewindable, 556 .rewind = snd_pcm_plugin_rewind, 557 .forwardable = snd_pcm_plugin_forwardable, 558 .forward = snd_pcm_plugin_forward, 559 .resume = snd_pcm_generic_resume, 560 .link = snd_pcm_generic_link, 561 .link_slaves = snd_pcm_generic_link_slaves, 562 .unlink = snd_pcm_generic_unlink, 563 .writei = snd_pcm_plugin_writei, 564 .writen = snd_pcm_plugin_writen, 565 .readi = snd_pcm_plugin_readi, 566 .readn = snd_pcm_plugin_readn, 567 .avail_update = snd_pcm_plugin_avail_update, 568 .mmap_commit = snd_pcm_plugin_mmap_commit, 569 .htimestamp = snd_pcm_generic_htimestamp, 570 .poll_descriptors_count = snd_pcm_generic_poll_descriptors_count, 571 .poll_descriptors = snd_pcm_generic_poll_descriptors, 572 .poll_revents = snd_pcm_generic_poll_revents, 573}; 574 575#endif 576