e85dfaaa6f767d36383b2793fe4a0bdd9f97ffbb
[platform/upstream/alsa-lib.git] / src / pcm / pcm_linear.c
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 */
37 const char *_snd_module_pcm_linear = "";
38 #endif
39
40 #ifndef DOC_HIDDEN
41 typedef 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
53 int 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
79 int 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
110 int 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
115 int 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
146 int 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
177 void 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
211 void 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
249 static 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
269 static 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
281 static 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         
299 static 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
317 static 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
327 static 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
362 static snd_pcm_uframes_t
363 snd_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
387 static snd_pcm_uframes_t
388 snd_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
412 static 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
425 static 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  */
453 int 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
496 This plugin converts linear samples from master linear conversion PCM to given
497 slave PCM. The channel count, format and rate must match for both of them.
498
499 \code
500 pcm.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  */
535 int _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
581 SND_DLSYM_BUILD_VERSION(_snd_pcm_linear_open, SND_PCM_DLSYM_VERSION);
582 #endif