Initial Import
[profile/ivi/alsa-lib.git] / src / pcm / pcm_alaw.c
1 /**
2  * \file pcm/pcm_alaw.c
3  * \ingroup PCM_Plugins
4  * \brief PCM A-Law Conversion Plugin Interface
5  * \author Abramo Bagnara <abramo@alsa-project.org>
6  * \date 2000-2001
7  */
8 /*
9  *  PCM - A-Law 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_alaw = "";
38 #endif
39
40 #ifndef DOC_HIDDEN
41
42 typedef void (*alaw_f)(const snd_pcm_channel_area_t *dst_areas,
43                        snd_pcm_uframes_t dst_offset,
44                        const snd_pcm_channel_area_t *src_areas,
45                        snd_pcm_uframes_t src_offset,
46                        unsigned int channels, snd_pcm_uframes_t frames,
47                        unsigned int getputidx);
48
49 typedef struct {
50         /* This field need to be the first */
51         snd_pcm_plugin_t plug;
52         unsigned int getput_idx;
53         alaw_f func;
54         snd_pcm_format_t sformat;
55 } snd_pcm_alaw_t;
56
57 #endif
58
59 static inline int val_seg(int val)
60 {
61         int r = 1;
62         val >>= 8;
63         if (val & 0xf0) {
64                 val >>= 4;
65                 r += 4;
66         }
67         if (val & 0x0c) {
68                 val >>= 2;
69                 r += 2;
70         }
71         if (val & 0x02)
72                 r += 1;
73         return r;
74 }
75
76 /*
77  * s16_to_alaw() - Convert a 16-bit linear PCM value to 8-bit A-law
78  *
79  * s16_to_alaw() accepts an 16-bit integer and encodes it as A-law data.
80  *
81  *              Linear Input Code       Compressed Code
82  *      ------------------------        ---------------
83  *      0000000wxyza                    000wxyz
84  *      0000001wxyza                    001wxyz
85  *      000001wxyzab                    010wxyz
86  *      00001wxyzabc                    011wxyz
87  *      0001wxyzabcd                    100wxyz
88  *      001wxyzabcde                    101wxyz
89  *      01wxyzabcdef                    110wxyz
90  *      1wxyzabcdefg                    111wxyz
91  *
92  * For further information see John C. Bellamy's Digital Telephony, 1982,
93  * John Wiley & Sons, pps 98-111 and 472-476.
94  */
95
96 static unsigned char s16_to_alaw(int pcm_val)
97 {
98         int             mask;
99         int             seg;
100         unsigned char   aval;
101
102         if (pcm_val >= 0) {
103                 mask = 0xD5;
104         } else {
105                 mask = 0x55;
106                 pcm_val = -pcm_val;
107                 if (pcm_val > 0x7fff)
108                         pcm_val = 0x7fff;
109         }
110
111         if (pcm_val < 256)
112                 aval = pcm_val >> 4;
113         else {
114                 /* Convert the scaled magnitude to segment number. */
115                 seg = val_seg(pcm_val);
116                 aval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f);
117         }
118         return aval ^ mask;
119 }
120
121 /*
122  * alaw_to_s16() - Convert an A-law value to 16-bit linear PCM
123  *
124  */
125 static int alaw_to_s16(unsigned char a_val)
126 {
127         int             t;
128         int             seg;
129
130         a_val ^= 0x55;
131         t = a_val & 0x7f;
132         if (t < 16)
133                 t = (t << 4) + 8;
134         else {
135                 seg = (t >> 4) & 0x07;
136                 t = ((t & 0x0f) << 4) + 0x108;
137                 t <<= seg -1;
138         }
139         return ((a_val & 0x80) ? t : -t);
140 }
141
142 #ifndef DOC_HIDDEN
143
144 void snd_pcm_alaw_decode(const snd_pcm_channel_area_t *dst_areas,
145                          snd_pcm_uframes_t dst_offset,
146                          const snd_pcm_channel_area_t *src_areas,
147                          snd_pcm_uframes_t src_offset,
148                          unsigned int channels, snd_pcm_uframes_t frames,
149                          unsigned int putidx)
150 {
151 #define PUT16_LABELS
152 #include "plugin_ops.h"
153 #undef PUT16_LABELS
154         void *put = put16_labels[putidx];
155         unsigned int channel;
156         for (channel = 0; channel < channels; ++channel) {
157                 const unsigned 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                         int16_t sample = alaw_to_s16(*src);
170                         goto *put;
171 #define PUT16_END after
172 #include "plugin_ops.h"
173 #undef PUT16_END
174                 after:
175                         src += src_step;
176                         dst += dst_step;
177                 }
178         }
179 }
180
181 void snd_pcm_alaw_encode(const snd_pcm_channel_area_t *dst_areas,
182                          snd_pcm_uframes_t dst_offset,
183                          const snd_pcm_channel_area_t *src_areas,
184                          snd_pcm_uframes_t src_offset,
185                          unsigned int channels, snd_pcm_uframes_t frames,
186                          unsigned int getidx)
187 {
188 #define GET16_LABELS
189 #include "plugin_ops.h"
190 #undef GET16_LABELS
191         void *get = get16_labels[getidx];
192         unsigned int channel;
193         int16_t sample = 0;
194         for (channel = 0; channel < channels; ++channel) {
195                 const char *src;
196                 char *dst;
197                 int src_step, dst_step;
198                 snd_pcm_uframes_t frames1;
199                 const snd_pcm_channel_area_t *src_area = &src_areas[channel];
200                 const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
201                 src = snd_pcm_channel_area_addr(src_area, src_offset);
202                 dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
203                 src_step = snd_pcm_channel_area_step(src_area);
204                 dst_step = snd_pcm_channel_area_step(dst_area);
205                 frames1 = frames;
206                 while (frames1-- > 0) {
207                         goto *get;
208 #define GET16_END after
209 #include "plugin_ops.h"
210 #undef GET16_END
211                 after:
212                         *dst = s16_to_alaw(sample);
213                         src += src_step;
214                         dst += dst_step;
215                 }
216         }
217 }
218
219 #endif /* DOC_HIDDEN */
220
221 static int snd_pcm_alaw_hw_refine_cprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
222 {
223         snd_pcm_alaw_t *alaw = pcm->private_data;
224         int err;
225         snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
226         err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
227                                          &access_mask);
228         if (err < 0)
229                 return err;
230         if (alaw->sformat == SND_PCM_FORMAT_A_LAW) {
231                 snd_pcm_format_mask_t format_mask = { SND_PCM_FMTBIT_LINEAR };
232                 err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_FORMAT,
233                                                  &format_mask);
234         } else {
235                 err = _snd_pcm_hw_params_set_format(params, 
236                                                    SND_PCM_FORMAT_A_LAW);
237         }
238         if (err < 0)
239                 return err;
240         err = _snd_pcm_hw_params_set_subformat(params, SND_PCM_SUBFORMAT_STD);
241         if (err < 0)
242                 return err;
243         params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
244         return 0;
245 }
246
247 static int snd_pcm_alaw_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)
248 {
249         snd_pcm_alaw_t *alaw = pcm->private_data;
250         snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
251         _snd_pcm_hw_params_any(sparams);
252         _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
253                                    &saccess_mask);
254         _snd_pcm_hw_params_set_format(sparams, alaw->sformat);
255         _snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD);
256         return 0;
257 }
258
259 static int snd_pcm_alaw_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
260                                             snd_pcm_hw_params_t *sparams)
261 {
262         int err;
263         unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
264                               SND_PCM_HW_PARBIT_RATE |
265                               SND_PCM_HW_PARBIT_PERIOD_SIZE |
266                               SND_PCM_HW_PARBIT_BUFFER_SIZE |
267                               SND_PCM_HW_PARBIT_PERIODS |
268                               SND_PCM_HW_PARBIT_PERIOD_TIME |
269                               SND_PCM_HW_PARBIT_BUFFER_TIME |
270                               SND_PCM_HW_PARBIT_TICK_TIME);
271         err = _snd_pcm_hw_params_refine(sparams, links, params);
272         if (err < 0)
273                 return err;
274         return 0;
275 }
276         
277 static int snd_pcm_alaw_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
278                                             snd_pcm_hw_params_t *sparams)
279 {
280         int err;
281         unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
282                               SND_PCM_HW_PARBIT_RATE |
283                               SND_PCM_HW_PARBIT_PERIOD_SIZE |
284                               SND_PCM_HW_PARBIT_BUFFER_SIZE |
285                               SND_PCM_HW_PARBIT_PERIODS |
286                               SND_PCM_HW_PARBIT_PERIOD_TIME |
287                               SND_PCM_HW_PARBIT_BUFFER_TIME |
288                               SND_PCM_HW_PARBIT_TICK_TIME);
289         err = _snd_pcm_hw_params_refine(params, links, sparams);
290         if (err < 0)
291                 return err;
292         return 0;
293 }
294
295 static int snd_pcm_alaw_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
296 {
297         return snd_pcm_hw_refine_slave(pcm, params,
298                                        snd_pcm_alaw_hw_refine_cprepare,
299                                        snd_pcm_alaw_hw_refine_cchange,
300                                        snd_pcm_alaw_hw_refine_sprepare,
301                                        snd_pcm_alaw_hw_refine_schange,
302                                        snd_pcm_generic_hw_refine);
303 }
304
305 static int snd_pcm_alaw_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
306 {
307         snd_pcm_alaw_t *alaw = pcm->private_data;
308         snd_pcm_format_t format;
309         int err = snd_pcm_hw_params_slave(pcm, params,
310                                           snd_pcm_alaw_hw_refine_cchange,
311                                           snd_pcm_alaw_hw_refine_sprepare,
312                                           snd_pcm_alaw_hw_refine_schange,
313                                           snd_pcm_generic_hw_params);
314         if (err < 0)
315                 return err;
316
317         err = INTERNAL(snd_pcm_hw_params_get_format)(params, &format);
318         if (err < 0)
319                 return err;
320                 
321         if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
322                 if (alaw->sformat == SND_PCM_FORMAT_A_LAW) {
323                         alaw->getput_idx = snd_pcm_linear_get_index(format, SND_PCM_FORMAT_S16);
324                         alaw->func = snd_pcm_alaw_encode;
325                 } else {
326                         alaw->getput_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, alaw->sformat);
327                         alaw->func = snd_pcm_alaw_decode;
328                 }
329         } else {
330                 if (alaw->sformat == SND_PCM_FORMAT_A_LAW) {
331                         alaw->getput_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, format);
332                         alaw->func = snd_pcm_alaw_decode;
333                 } else {
334                         alaw->getput_idx = snd_pcm_linear_get_index(alaw->sformat, SND_PCM_FORMAT_S16);
335                         alaw->func = snd_pcm_alaw_encode;
336                 }
337         }
338         return 0;
339 }
340
341 static snd_pcm_uframes_t
342 snd_pcm_alaw_write_areas(snd_pcm_t *pcm,
343                          const snd_pcm_channel_area_t *areas,
344                          snd_pcm_uframes_t offset,
345                          snd_pcm_uframes_t size,
346                          const snd_pcm_channel_area_t *slave_areas,
347                          snd_pcm_uframes_t slave_offset,
348                          snd_pcm_uframes_t *slave_sizep)
349 {
350         snd_pcm_alaw_t *alaw = pcm->private_data;
351         if (size > *slave_sizep)
352                 size = *slave_sizep;
353         alaw->func(slave_areas, slave_offset,
354                    areas, offset, 
355                    pcm->channels, size,
356                    alaw->getput_idx);
357         *slave_sizep = size;
358         return size;
359 }
360
361 static snd_pcm_uframes_t
362 snd_pcm_alaw_read_areas(snd_pcm_t *pcm,
363                         const snd_pcm_channel_area_t *areas,
364                         snd_pcm_uframes_t offset,
365                         snd_pcm_uframes_t size,
366                         const snd_pcm_channel_area_t *slave_areas,
367                         snd_pcm_uframes_t slave_offset,
368                         snd_pcm_uframes_t *slave_sizep)
369 {
370         snd_pcm_alaw_t *alaw = pcm->private_data;
371         if (size > *slave_sizep)
372                 size = *slave_sizep;
373         alaw->func(areas, offset, 
374                    slave_areas, slave_offset,
375                    pcm->channels, size,
376                    alaw->getput_idx);
377         *slave_sizep = size;
378         return size;
379 }
380
381 static void snd_pcm_alaw_dump(snd_pcm_t *pcm, snd_output_t *out)
382 {
383         snd_pcm_alaw_t *alaw = pcm->private_data;
384         snd_output_printf(out, "A-Law conversion PCM (%s)\n", 
385                 snd_pcm_format_name(alaw->sformat));
386         if (pcm->setup) {
387                 snd_output_printf(out, "Its setup is:\n");
388                 snd_pcm_dump_setup(pcm, out);
389         }
390         snd_output_printf(out, "Slave: ");
391         snd_pcm_dump(alaw->plug.gen.slave, out);
392 }
393
394 static const snd_pcm_ops_t snd_pcm_alaw_ops = {
395         .close = snd_pcm_generic_close,
396         .info = snd_pcm_generic_info,
397         .hw_refine = snd_pcm_alaw_hw_refine,
398         .hw_params = snd_pcm_alaw_hw_params,
399         .hw_free = snd_pcm_generic_hw_free,
400         .sw_params = snd_pcm_generic_sw_params,
401         .channel_info = snd_pcm_generic_channel_info,
402         .dump = snd_pcm_alaw_dump,
403         .nonblock = snd_pcm_generic_nonblock,
404         .async = snd_pcm_generic_async,
405         .mmap = snd_pcm_generic_mmap,
406         .munmap = snd_pcm_generic_munmap,
407 };
408
409 /**
410  * \brief Creates a new A-Law conversion PCM
411  * \param pcmp Returns created PCM handle
412  * \param name Name of PCM
413  * \param sformat Slave (destination) format
414  * \param slave Slave PCM handle
415  * \param close_slave When set, the slave PCM handle is closed with copy PCM
416  * \retval zero on success otherwise a negative error code
417  * \warning Using of this function might be dangerous in the sense
418  *          of compatibility reasons. The prototype might be freely
419  *          changed in future.
420  */           
421 int snd_pcm_alaw_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, snd_pcm_t *slave, int close_slave)
422 {
423         snd_pcm_t *pcm;
424         snd_pcm_alaw_t *alaw;
425         int err;
426         assert(pcmp && slave);
427         if (snd_pcm_format_linear(sformat) != 1 &&
428             sformat != SND_PCM_FORMAT_A_LAW)
429                 return -EINVAL;
430         alaw = calloc(1, sizeof(snd_pcm_alaw_t));
431         if (!alaw) {
432                 return -ENOMEM;
433         }
434         snd_pcm_plugin_init(&alaw->plug);
435         alaw->sformat = sformat;
436         alaw->plug.read = snd_pcm_alaw_read_areas;
437         alaw->plug.write = snd_pcm_alaw_write_areas;
438         alaw->plug.undo_read = snd_pcm_plugin_undo_read_generic;
439         alaw->plug.undo_write = snd_pcm_plugin_undo_write_generic;
440         alaw->plug.gen.slave = slave;
441         alaw->plug.gen.close_slave = close_slave;
442
443         err = snd_pcm_new(&pcm, SND_PCM_TYPE_ALAW, name, slave->stream, slave->mode);
444         if (err < 0) {
445                 free(alaw);
446                 return err;
447         }
448         pcm->ops = &snd_pcm_alaw_ops;
449         pcm->fast_ops = &snd_pcm_plugin_fast_ops;
450         pcm->private_data = alaw;
451         pcm->poll_fd = slave->poll_fd;
452         pcm->poll_events = slave->poll_events;
453         pcm->monotonic = slave->monotonic;
454         snd_pcm_set_hw_ptr(pcm, &alaw->plug.hw_ptr, -1, 0);
455         snd_pcm_set_appl_ptr(pcm, &alaw->plug.appl_ptr, -1, 0);
456         *pcmp = pcm;
457
458         return 0;
459 }
460
461 /*! \page pcm_plugins
462
463 \section pcm_plugins_alaw Plugin: A-Law
464
465 This plugin converts A-Law samples to linear or linear to A-Law samples
466 from master A-Law conversion PCM to given slave PCM. The channel count,
467 format and rate must match for both of them.
468
469 \code
470 pcm.name {
471         type alaw               # A-Law conversion PCM
472         slave STR               # Slave name
473         # or
474         slave {                 # Slave definition
475                 pcm STR         # Slave PCM name
476                 # or
477                 pcm { }         # Slave PCM definition
478                 format STR      # Slave format
479         }
480 }
481 \endcode
482
483 \subsection pcm_plugins_alaw_funcref Function reference
484
485 <UL>
486   <LI>snd_pcm_alaw_open()
487   <LI>_snd_pcm_alaw_open()
488 </UL>
489
490 */
491
492 /**
493  * \brief Creates a new A-Law conversion PCM
494  * \param pcmp Returns created PCM handle
495  * \param name Name of PCM
496  * \param root Root configuration node
497  * \param conf Configuration node with copy PCM description
498  * \param stream Stream type
499  * \param mode Stream mode
500  * \retval zero on success otherwise a negative error code
501  * \warning Using of this function might be dangerous in the sense
502  *          of compatibility reasons. The prototype might be freely
503  *          changed in future.
504  */
505 int _snd_pcm_alaw_open(snd_pcm_t **pcmp, const char *name,
506                        snd_config_t *root, snd_config_t *conf, 
507                        snd_pcm_stream_t stream, int mode)
508 {
509         snd_config_iterator_t i, next;
510         int err;
511         snd_pcm_t *spcm;
512         snd_config_t *slave = NULL, *sconf;
513         snd_pcm_format_t sformat;
514         snd_config_for_each(i, next, conf) {
515                 snd_config_t *n = snd_config_iterator_entry(i);
516                 const char *id;
517                 if (snd_config_get_id(n, &id) < 0)
518                         continue;
519                 if (snd_pcm_conf_generic_id(id))
520                         continue;
521                 if (strcmp(id, "slave") == 0) {
522                         slave = n;
523                         continue;
524                 }
525                 SNDERR("Unknown field %s", id);
526                 return -EINVAL;
527         }
528         if (!slave) {
529                 SNDERR("slave is not defined");
530                 return -EINVAL;
531         }
532         err = snd_pcm_slave_conf(root, slave, &sconf, 1,
533                                  SND_PCM_HW_PARAM_FORMAT, SCONF_MANDATORY, &sformat);
534         if (err < 0)
535                 return err;
536         if (snd_pcm_format_linear(sformat) != 1 &&
537             sformat != SND_PCM_FORMAT_A_LAW) {
538                 snd_config_delete(sconf);
539                 SNDERR("invalid slave format");
540                 return -EINVAL;
541         }
542         err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
543         snd_config_delete(sconf);
544         if (err < 0)
545                 return err;
546         err = snd_pcm_alaw_open(pcmp, name, sformat, spcm, 1);
547         if (err < 0)
548                 snd_pcm_close(spcm);
549         return err;
550 }
551 #ifndef DOC_HIDDEN
552 SND_DLSYM_BUILD_VERSION(_snd_pcm_alaw_open, SND_PCM_DLSYM_VERSION);
553 #endif