OSDN Git Service

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