1/** 2 * \file pcm/pcm_linear.c 3 * \ingroup PCM_Plugins 4 * \brief PCM Linear Conversion Plugin Interface 5 * \author Abramo Bagnara <abramo@alsa-project.org> 6 * \date 2000-2001 7 */ 8/* 9 * PCM - Linear conversion 10 * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> 11 * 12 * 13 * This library is free software; you can redistribute it and/or modify 14 * it under the terms of the GNU Lesser General Public License as 15 * published by the Free Software Foundation; either version 2.1 of 16 * the License, or (at your option) any later version. 17 * 18 * This program is distributed in the hope that it will be useful, 19 * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 * GNU Lesser General Public License for more details. 22 * 23 * You should have received a copy of the GNU Lesser General Public 24 * License along with this library; if not, write to the Free Software 25 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 26 * 27 */ 28 29#include <byteswap.h> 30#include "pcm_local.h" 31#include "pcm_plugin.h" 32 33#include "plugin_ops.h" 34 35#ifndef PIC 36/* entry for static linking */ 37const char *_snd_module_pcm_linear = ""; 38#endif 39 40#ifndef DOC_HIDDEN 41typedef struct { 42 /* This field need to be the first */ 43 snd_pcm_plugin_t plug; 44 unsigned int use_getput; 45 unsigned int conv_idx; 46 unsigned int get_idx, put_idx; 47 snd_pcm_format_t sformat; 48} snd_pcm_linear_t; 49#endif 50 51#ifndef DOC_HIDDEN 52 53int snd_pcm_linear_convert_index(snd_pcm_format_t src_format, 54 snd_pcm_format_t dst_format) 55{ 56 int src_endian, dst_endian, sign, src_width, dst_width; 57 58 sign = (snd_pcm_format_signed(src_format) != 59 snd_pcm_format_signed(dst_format)); 60#ifdef SND_LITTLE_ENDIAN 61 src_endian = snd_pcm_format_big_endian(src_format); 62 dst_endian = snd_pcm_format_big_endian(dst_format); 63#else 64 src_endian = snd_pcm_format_little_endian(src_format); 65 dst_endian = snd_pcm_format_little_endian(dst_format); 66#endif 67 68 if (src_endian < 0) 69 src_endian = 0; 70 if (dst_endian < 0) 71 dst_endian = 0; 72 73 src_width = snd_pcm_format_width(src_format) / 8 - 1; 74 dst_width = snd_pcm_format_width(dst_format) / 8 - 1; 75 76 return src_width * 32 + src_endian * 16 + sign * 8 + dst_width * 2 + dst_endian; 77} 78 79int snd_pcm_linear_get_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format) 80{ 81 int sign, width, pwidth, endian; 82 sign = (snd_pcm_format_signed(src_format) != 83 snd_pcm_format_signed(dst_format)); 84#ifdef SND_LITTLE_ENDIAN 85 endian = snd_pcm_format_big_endian(src_format); 86#else 87 endian = snd_pcm_format_little_endian(src_format); 88#endif 89 if (endian < 0) 90 endian = 0; 91 pwidth = snd_pcm_format_physical_width(src_format); 92 width = snd_pcm_format_width(src_format); 93 if (pwidth == 24) { 94 switch (width) { 95 case 24: 96 width = 0; break; 97 case 20: 98 width = 1; break; 99 case 18: 100 default: 101 width = 2; break; 102 } 103 return width * 4 + endian * 2 + sign + 16; 104 } else { 105 width = width / 8 - 1; 106 return width * 4 + endian * 2 + sign; 107 } 108} 109 110int snd_pcm_linear_get32_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format) 111{ 112 return snd_pcm_linear_get_index(src_format, dst_format); 113} 114 115int snd_pcm_linear_put_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format) 116{ 117 int sign, width, pwidth, endian; 118 sign = (snd_pcm_format_signed(src_format) != 119 snd_pcm_format_signed(dst_format)); 120#ifdef SND_LITTLE_ENDIAN 121 endian = snd_pcm_format_big_endian(dst_format); 122#else 123 endian = snd_pcm_format_little_endian(dst_format); 124#endif 125 if (endian < 0) 126 endian = 0; 127 pwidth = snd_pcm_format_physical_width(dst_format); 128 width = snd_pcm_format_width(dst_format); 129 if (pwidth == 24) { 130 switch (width) { 131 case 24: 132 width = 0; break; 133 case 20: 134 width = 1; break; 135 case 18: 136 default: 137 width = 2; break; 138 } 139 return width * 4 + endian * 2 + sign + 16; 140 } else { 141 width = width / 8 - 1; 142 return width * 4 + endian * 2 + sign; 143 } 144} 145 146int snd_pcm_linear_put32_index(snd_pcm_format_t src_format, snd_pcm_format_t dst_format) 147{ 148 int sign, width, pwidth, endian; 149 sign = (snd_pcm_format_signed(src_format) != 150 snd_pcm_format_signed(dst_format)); 151#ifdef SND_LITTLE_ENDIAN 152 endian = snd_pcm_format_big_endian(dst_format); 153#else 154 endian = snd_pcm_format_little_endian(dst_format); 155#endif 156 if (endian < 0) 157 endian = 0; 158 pwidth = snd_pcm_format_physical_width(dst_format); 159 width = snd_pcm_format_width(dst_format); 160 if (pwidth == 24) { 161 switch (width) { 162 case 24: 163 width = 0; break; 164 case 20: 165 width = 1; break; 166 case 18: 167 default: 168 width = 2; break; 169 } 170 return width * 4 + endian * 2 + sign + 16; 171 } else { 172 width = width / 8 - 1; 173 return width * 4 + endian * 2 + sign; 174 } 175} 176 177void snd_pcm_linear_convert(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset, 178 const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset, 179 unsigned int channels, snd_pcm_uframes_t frames, 180 unsigned int convidx) 181{ 182#define CONV_LABELS 183#include "plugin_ops.h" 184#undef CONV_LABELS 185 void *conv = conv_labels[convidx]; 186 unsigned int channel; 187 for (channel = 0; channel < channels; ++channel) { 188 const char *src; 189 char *dst; 190 int src_step, dst_step; 191 snd_pcm_uframes_t frames1; 192 const snd_pcm_channel_area_t *src_area = &src_areas[channel]; 193 const snd_pcm_channel_area_t *dst_area = &dst_areas[channel]; 194 src = snd_pcm_channel_area_addr(src_area, src_offset); 195 dst = snd_pcm_channel_area_addr(dst_area, dst_offset); 196 src_step = snd_pcm_channel_area_step(src_area); 197 dst_step = snd_pcm_channel_area_step(dst_area); 198 frames1 = frames; 199 while (frames1-- > 0) { 200 goto *conv; 201#define CONV_END after 202#include "plugin_ops.h" 203#undef CONV_END 204 after: 205 src += src_step; 206 dst += dst_step; 207 } 208 } 209} 210 211void snd_pcm_linear_getput(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset, 212 const snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset, 213 unsigned int channels, snd_pcm_uframes_t frames, 214 unsigned int get_idx, unsigned int put_idx) 215{ 216#define CONV24_LABELS 217#include "plugin_ops.h" 218#undef CONV24_LABELS 219 void *get = get32_labels[get_idx]; 220 void *put = put32_labels[put_idx]; 221 unsigned int channel; 222 u_int32_t sample = 0; 223 for (channel = 0; channel < channels; ++channel) { 224 const char *src; 225 char *dst; 226 int src_step, dst_step; 227 snd_pcm_uframes_t frames1; 228 const snd_pcm_channel_area_t *src_area = &src_areas[channel]; 229 const snd_pcm_channel_area_t *dst_area = &dst_areas[channel]; 230 src = snd_pcm_channel_area_addr(src_area, src_offset); 231 dst = snd_pcm_channel_area_addr(dst_area, dst_offset); 232 src_step = snd_pcm_channel_area_step(src_area); 233 dst_step = snd_pcm_channel_area_step(dst_area); 234 frames1 = frames; 235 while (frames1-- > 0) { 236 goto *get; 237#define CONV24_END after 238#include "plugin_ops.h" 239#undef CONV24_END 240 after: 241 src += src_step; 242 dst += dst_step; 243 } 244 } 245} 246 247#endif /* DOC_HIDDEN */ 248 249static int snd_pcm_linear_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params) 250{ 251 int err; 252 snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM }; 253 snd_pcm_format_mask_t format_mask = { SND_PCM_FMTBIT_LINEAR }; 254 err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS, 255 &access_mask); 256 if (err < 0) 257 return err; 258 err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_FORMAT, 259 &format_mask); 260 if (err < 0) 261 return err; 262 err = _snd_pcm_hw_params_set_subformat(params, SND_PCM_SUBFORMAT_STD); 263 if (err < 0) 264 return err; 265 params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID); 266 return 0; 267} 268 269static int snd_pcm_linear_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams) 270{ 271 snd_pcm_linear_t *linear = pcm->private_data; 272 snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP }; 273 _snd_pcm_hw_params_any(sparams); 274 _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS, 275 &saccess_mask); 276 _snd_pcm_hw_params_set_format(sparams, linear->sformat); 277 _snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD); 278 return 0; 279} 280 281static int snd_pcm_linear_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params, 282 snd_pcm_hw_params_t *sparams) 283{ 284 int err; 285 unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS | 286 SND_PCM_HW_PARBIT_RATE | 287 SND_PCM_HW_PARBIT_PERIOD_SIZE | 288 SND_PCM_HW_PARBIT_BUFFER_SIZE | 289 SND_PCM_HW_PARBIT_PERIODS | 290 SND_PCM_HW_PARBIT_PERIOD_TIME | 291 SND_PCM_HW_PARBIT_BUFFER_TIME | 292 SND_PCM_HW_PARBIT_TICK_TIME); 293 err = _snd_pcm_hw_params_refine(sparams, links, params); 294 if (err < 0) 295 return err; 296 return 0; 297} 298 299static int snd_pcm_linear_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params, 300 snd_pcm_hw_params_t *sparams) 301{ 302 int err; 303 unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS | 304 SND_PCM_HW_PARBIT_RATE | 305 SND_PCM_HW_PARBIT_PERIOD_SIZE | 306 SND_PCM_HW_PARBIT_BUFFER_SIZE | 307 SND_PCM_HW_PARBIT_PERIODS | 308 SND_PCM_HW_PARBIT_PERIOD_TIME | 309 SND_PCM_HW_PARBIT_BUFFER_TIME | 310 SND_PCM_HW_PARBIT_TICK_TIME); 311 err = _snd_pcm_hw_params_refine(params, links, sparams); 312 if (err < 0) 313 return err; 314 return 0; 315} 316 317static int snd_pcm_linear_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) 318{ 319 return snd_pcm_hw_refine_slave(pcm, params, 320 snd_pcm_linear_hw_refine_cprepare, 321 snd_pcm_linear_hw_refine_cchange, 322 snd_pcm_linear_hw_refine_sprepare, 323 snd_pcm_linear_hw_refine_schange, 324 snd_pcm_generic_hw_refine); 325} 326 327static int snd_pcm_linear_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) 328{ 329 snd_pcm_linear_t *linear = pcm->private_data; 330 snd_pcm_format_t format; 331 int err = snd_pcm_hw_params_slave(pcm, params, 332 snd_pcm_linear_hw_refine_cchange, 333 snd_pcm_linear_hw_refine_sprepare, 334 snd_pcm_linear_hw_refine_schange, 335 snd_pcm_generic_hw_params); 336 if (err < 0) 337 return err; 338 err = INTERNAL(snd_pcm_hw_params_get_format)(params, &format); 339 if (err < 0) 340 return err; 341 linear->use_getput = (snd_pcm_format_physical_width(format) == 24 || 342 snd_pcm_format_physical_width(linear->sformat) == 24); 343 if (linear->use_getput) { 344 if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { 345 linear->get_idx = snd_pcm_linear_get32_index(format, SND_PCM_FORMAT_S32); 346 linear->put_idx = snd_pcm_linear_put32_index(SND_PCM_FORMAT_S32, linear->sformat); 347 } else { 348 linear->get_idx = snd_pcm_linear_get32_index(linear->sformat, SND_PCM_FORMAT_S32); 349 linear->put_idx = snd_pcm_linear_put32_index(SND_PCM_FORMAT_S32, format); 350 } 351 } else { 352 if (pcm->stream == SND_PCM_STREAM_PLAYBACK) 353 linear->conv_idx = snd_pcm_linear_convert_index(format, 354 linear->sformat); 355 else 356 linear->conv_idx = snd_pcm_linear_convert_index(linear->sformat, 357 format); 358 } 359 return 0; 360} 361 362static snd_pcm_uframes_t 363snd_pcm_linear_write_areas(snd_pcm_t *pcm, 364 const snd_pcm_channel_area_t *areas, 365 snd_pcm_uframes_t offset, 366 snd_pcm_uframes_t size, 367 const snd_pcm_channel_area_t *slave_areas, 368 snd_pcm_uframes_t slave_offset, 369 snd_pcm_uframes_t *slave_sizep) 370{ 371 snd_pcm_linear_t *linear = pcm->private_data; 372 if (size > *slave_sizep) 373 size = *slave_sizep; 374 if (linear->use_getput) 375 snd_pcm_linear_getput(slave_areas, slave_offset, 376 areas, offset, 377 pcm->channels, size, 378 linear->get_idx, linear->put_idx); 379 else 380 snd_pcm_linear_convert(slave_areas, slave_offset, 381 areas, offset, 382 pcm->channels, size, linear->conv_idx); 383 *slave_sizep = size; 384 return size; 385} 386 387static snd_pcm_uframes_t 388snd_pcm_linear_read_areas(snd_pcm_t *pcm, 389 const snd_pcm_channel_area_t *areas, 390 snd_pcm_uframes_t offset, 391 snd_pcm_uframes_t size, 392 const snd_pcm_channel_area_t *slave_areas, 393 snd_pcm_uframes_t slave_offset, 394 snd_pcm_uframes_t *slave_sizep) 395{ 396 snd_pcm_linear_t *linear = pcm->private_data; 397 if (size > *slave_sizep) 398 size = *slave_sizep; 399 if (linear->use_getput) 400 snd_pcm_linear_getput(areas, offset, 401 slave_areas, slave_offset, 402 pcm->channels, size, 403 linear->get_idx, linear->put_idx); 404 else 405 snd_pcm_linear_convert(areas, offset, 406 slave_areas, slave_offset, 407 pcm->channels, size, linear->conv_idx); 408 *slave_sizep = size; 409 return size; 410} 411 412static void snd_pcm_linear_dump(snd_pcm_t *pcm, snd_output_t *out) 413{ 414 snd_pcm_linear_t *linear = pcm->private_data; 415 snd_output_printf(out, "Linear conversion PCM (%s)\n", 416 snd_pcm_format_name(linear->sformat)); 417 if (pcm->setup) { 418 snd_output_printf(out, "Its setup is:\n"); 419 snd_pcm_dump_setup(pcm, out); 420 } 421 snd_output_printf(out, "Slave: "); 422 snd_pcm_dump(linear->plug.gen.slave, out); 423} 424 425static const snd_pcm_ops_t snd_pcm_linear_ops = { 426 .close = snd_pcm_generic_close, 427 .info = snd_pcm_generic_info, 428 .hw_refine = snd_pcm_linear_hw_refine, 429 .hw_params = snd_pcm_linear_hw_params, 430 .hw_free = snd_pcm_generic_hw_free, 431 .sw_params = snd_pcm_generic_sw_params, 432 .channel_info = snd_pcm_generic_channel_info, 433 .dump = snd_pcm_linear_dump, 434 .nonblock = snd_pcm_generic_nonblock, 435 .async = snd_pcm_generic_async, 436 .mmap = snd_pcm_generic_mmap, 437 .munmap = snd_pcm_generic_munmap, 438}; 439 440 441/** 442 * \brief Creates a new linear conversion PCM 443 * \param pcmp Returns created PCM handle 444 * \param name Name of PCM 445 * \param sformat Slave (destination) format 446 * \param slave Slave PCM handle 447 * \param close_slave When set, the slave PCM handle is closed with copy PCM 448 * \retval zero on success otherwise a negative error code 449 * \warning Using of this function might be dangerous in the sense 450 * of compatibility reasons. The prototype might be freely 451 * changed in future. 452 */ 453int snd_pcm_linear_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, snd_pcm_t *slave, int close_slave) 454{ 455 snd_pcm_t *pcm; 456 snd_pcm_linear_t *linear; 457 int err; 458 assert(pcmp && slave); 459 if (snd_pcm_format_linear(sformat) != 1) 460 return -EINVAL; 461 linear = calloc(1, sizeof(snd_pcm_linear_t)); 462 if (!linear) { 463 return -ENOMEM; 464 } 465 snd_pcm_plugin_init(&linear->plug); 466 linear->sformat = sformat; 467 linear->plug.read = snd_pcm_linear_read_areas; 468 linear->plug.write = snd_pcm_linear_write_areas; 469 linear->plug.undo_read = snd_pcm_plugin_undo_read_generic; 470 linear->plug.undo_write = snd_pcm_plugin_undo_write_generic; 471 linear->plug.gen.slave = slave; 472 linear->plug.gen.close_slave = close_slave; 473 474 err = snd_pcm_new(&pcm, SND_PCM_TYPE_LINEAR, name, slave->stream, slave->mode); 475 if (err < 0) { 476 free(linear); 477 return err; 478 } 479 pcm->ops = &snd_pcm_linear_ops; 480 pcm->fast_ops = &snd_pcm_plugin_fast_ops; 481 pcm->private_data = linear; 482 pcm->poll_fd = slave->poll_fd; 483 pcm->poll_events = slave->poll_events; 484 pcm->monotonic = slave->monotonic; 485 snd_pcm_set_hw_ptr(pcm, &linear->plug.hw_ptr, -1, 0); 486 snd_pcm_set_appl_ptr(pcm, &linear->plug.appl_ptr, -1, 0); 487 *pcmp = pcm; 488 489 return 0; 490} 491 492/*! \page pcm_plugins 493 494\section pcm_plugins_linear Plugin: linear 495 496This plugin converts linear samples from master linear conversion PCM to given 497slave PCM. The channel count, format and rate must match for both of them. 498 499\code 500pcm.name { 501 type linear # Linear conversion PCM 502 slave STR # Slave name 503 # or 504 slave { # Slave definition 505 pcm STR # Slave PCM name 506 # or 507 pcm { } # Slave PCM definition 508 format STR # Slave format 509 } 510} 511\endcode 512 513\subsection pcm_plugins_linear_funcref Function reference 514 515<UL> 516 <LI>snd_pcm_linear_open() 517 <LI>_snd_pcm_linear_open() 518</UL> 519 520*/ 521 522/** 523 * \brief Creates a new linear conversion PCM 524 * \param pcmp Returns created PCM handle 525 * \param name Name of PCM 526 * \param root Root configuration node 527 * \param conf Configuration node with copy PCM description 528 * \param stream Stream type 529 * \param mode Stream mode 530 * \retval zero on success otherwise a negative error code 531 * \warning Using of this function might be dangerous in the sense 532 * of compatibility reasons. The prototype might be freely 533 * changed in future. 534 */ 535int _snd_pcm_linear_open(snd_pcm_t **pcmp, const char *name, 536 snd_config_t *root, snd_config_t *conf, 537 snd_pcm_stream_t stream, int mode) 538{ 539 snd_config_iterator_t i, next; 540 int err; 541 snd_pcm_t *spcm; 542 snd_config_t *slave = NULL, *sconf; 543 snd_pcm_format_t sformat; 544 snd_config_for_each(i, next, conf) { 545 snd_config_t *n = snd_config_iterator_entry(i); 546 const char *id; 547 if (snd_config_get_id(n, &id) < 0) 548 continue; 549 if (snd_pcm_conf_generic_id(id)) 550 continue; 551 if (strcmp(id, "slave") == 0) { 552 slave = n; 553 continue; 554 } 555 SNDERR("Unknown field %s", id); 556 return -EINVAL; 557 } 558 if (!slave) { 559 SNDERR("slave is not defined"); 560 return -EINVAL; 561 } 562 err = snd_pcm_slave_conf(root, slave, &sconf, 1, 563 SND_PCM_HW_PARAM_FORMAT, SCONF_MANDATORY, &sformat); 564 if (err < 0) 565 return err; 566 if (snd_pcm_format_linear(sformat) != 1) { 567 snd_config_delete(sconf); 568 SNDERR("slave format is not linear"); 569 return -EINVAL; 570 } 571 err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf); 572 snd_config_delete(sconf); 573 if (err < 0) 574 return err; 575 err = snd_pcm_linear_open(pcmp, name, sformat, spcm, 1); 576 if (err < 0) 577 snd_pcm_close(spcm); 578 return err; 579} 580#ifndef DOC_HIDDEN 581SND_DLSYM_BUILD_VERSION(_snd_pcm_linear_open, SND_PCM_DLSYM_VERSION); 582#endif 583