xref: /third_party/alsa-lib/src/pcm/pcm_linear.c (revision d5ac70f0)
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