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