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