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