Change get_params prototype
[platform/adaptation/spreadtrum/audio-hal-sc7727.git] / tizen-audio-impl-pcm.c
1 /*
2  * audio-hal
3  *
4  * Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "tizen-audio-internal.h"
29 #include "tizen-audio-impl.h"
30
31 #ifndef __USE_TINYALSA__
32 #define DEVICE_NAME_MAX 32
33 #endif
34
35 #ifdef __USE_TINYALSA__
36 /* Convert pcm format from pulse to alsa */
37 static const uint32_t g_format_convert_table[] = {
38     [AUDIO_SAMPLE_U8]        = PCM_FORMAT_S8,
39     [AUDIO_SAMPLE_S16LE]     = PCM_FORMAT_S16_LE,
40     [AUDIO_SAMPLE_S32LE]     = PCM_FORMAT_S32_LE,
41     [AUDIO_SAMPLE_S24_32LE]  = PCM_FORMAT_S24_LE
42 };
43 #else  /* alsa-lib */
44 /* FIXME : To avoid build warning... */
45 int _snd_pcm_poll_descriptor(snd_pcm_t *pcm);
46 /* Convert pcm format from pulse to alsa */
47 static const uint32_t g_format_convert_table[] = {
48     [AUDIO_SAMPLE_U8]        = SND_PCM_FORMAT_U8,
49     [AUDIO_SAMPLE_ALAW]      = SND_PCM_FORMAT_A_LAW,
50     [AUDIO_SAMPLE_ULAW]      = SND_PCM_FORMAT_MU_LAW,
51     [AUDIO_SAMPLE_S16LE]     = SND_PCM_FORMAT_S16_LE,
52     [AUDIO_SAMPLE_S16BE]     = SND_PCM_FORMAT_S16_BE,
53     [AUDIO_SAMPLE_FLOAT32LE] = SND_PCM_FORMAT_FLOAT_LE,
54     [AUDIO_SAMPLE_FLOAT32BE] = SND_PCM_FORMAT_FLOAT_BE,
55     [AUDIO_SAMPLE_S32LE]     = SND_PCM_FORMAT_S32_LE,
56     [AUDIO_SAMPLE_S32BE]     = SND_PCM_FORMAT_S32_BE,
57     [AUDIO_SAMPLE_S24LE]     = SND_PCM_FORMAT_S24_3LE,
58     [AUDIO_SAMPLE_S24BE]     = SND_PCM_FORMAT_S24_3BE,
59     [AUDIO_SAMPLE_S24_32LE]  = SND_PCM_FORMAT_S24_LE,
60     [AUDIO_SAMPLE_S24_32BE]  = SND_PCM_FORMAT_S24_BE
61 };
62 #endif
63
64 static uint32_t __convert_format(audio_sample_format_s format)
65 {
66     return g_format_convert_table[format];
67 }
68
69 /* #define DEBUG_TIMING */
70
71 static int __voice_pcm_set_params(audio_hal_s *ah, snd_pcm_t *pcm)
72 {
73     snd_pcm_hw_params_t *params = NULL;
74     int err = 0;
75     unsigned int val = 0;
76
77     AUDIO_RETURN_VAL_IF_FAIL(ah, AUDIO_ERR_PARAMETER);
78     AUDIO_RETURN_VAL_IF_FAIL(pcm, AUDIO_ERR_PARAMETER);
79
80     /* Skip parameter setting to null device. */
81     if (snd_pcm_type(pcm) == SND_PCM_TYPE_NULL)
82         return AUDIO_ERR_IOCTL;
83
84     /* Allocate a hardware parameters object. */
85     snd_pcm_hw_params_alloca(&params);
86
87     /* Fill it in with default values. */
88     if (snd_pcm_hw_params_any(pcm, params) < 0) {
89         AUDIO_LOG_ERROR("snd_pcm_hw_params_any() : failed! - %s\n", snd_strerror(err));
90         goto error;
91     }
92
93     /* Set the desired hardware parameters. */
94     /* Interleaved mode */
95     err = snd_pcm_hw_params_set_access(pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED);
96     if (err < 0) {
97         AUDIO_LOG_ERROR("snd_pcm_hw_params_set_access() : failed! - %s\n", snd_strerror(err));
98         goto error;
99     }
100     err = snd_pcm_hw_params_set_rate(pcm, params, 8000, 0);
101     if (err < 0) {
102         AUDIO_LOG_ERROR("snd_pcm_hw_params_set_rate() : failed! - %s\n", snd_strerror(err));
103     }
104     err = snd_pcm_hw_params(pcm, params);
105     if (err < 0) {
106         AUDIO_LOG_ERROR("snd_pcm_hw_params() : failed! - %s\n", snd_strerror(err));
107         goto error;
108     }
109
110     /* Dump current param */
111     snd_pcm_hw_params_get_access(params, (snd_pcm_access_t *) &val);
112     AUDIO_LOG_DEBUG("access type = %s\n", snd_pcm_access_name((snd_pcm_access_t)val));
113
114     snd_pcm_hw_params_get_format(params, (snd_pcm_format_t*)&val);
115     AUDIO_LOG_DEBUG("format = '%s' (%s)\n",
116                     snd_pcm_format_name((snd_pcm_format_t)val),
117                     snd_pcm_format_description((snd_pcm_format_t)val));
118
119     snd_pcm_hw_params_get_subformat(params, (snd_pcm_subformat_t *)&val);
120     AUDIO_LOG_DEBUG("subformat = '%s' (%s)\n",
121                     snd_pcm_subformat_name((snd_pcm_subformat_t)val),
122                     snd_pcm_subformat_description((snd_pcm_subformat_t)val));
123
124     snd_pcm_hw_params_get_channels(params, &val);
125     AUDIO_LOG_DEBUG("channels = %d\n", val);
126
127     return 0;
128
129 error:
130     return -1;
131 }
132
133 #ifdef __USE_TINYALSA__
134 static int __parse_card_device_number(const char *card, const char *device, unsigned int *card_u, unsigned int *device_u) {
135     AUDIO_RETURN_VAL_IF_FAIL(card, AUDIO_ERR_PARAMETER);
136     AUDIO_RETURN_VAL_IF_FAIL(device, AUDIO_ERR_PARAMETER);
137     AUDIO_RETURN_VAL_IF_FAIL(card_u, AUDIO_ERR_PARAMETER);
138     AUDIO_RETURN_VAL_IF_FAIL(device_u, AUDIO_ERR_PARAMETER);
139
140     AUDIO_LOG_DEBUG("card : %s, device : %s", card, device);
141
142     *card_u = (unsigned int) strtol(card, NULL, 10);
143     *device_u = (unsigned int) strtol(device, NULL, 10);
144
145     return 0;
146 }
147
148 static struct pcm *__tinyalsa_open_device(const char *card, const char *device, audio_pcm_sample_spec_s *ss, size_t period_size, size_t period_count, uint32_t direction)
149 {
150     struct pcm *pcm = NULL;
151     struct pcm_config config;
152     unsigned int card_u, device_u;
153
154     AUDIO_RETURN_NULL_IF_FAIL(device);
155     AUDIO_RETURN_NULL_IF_FAIL(ss);
156
157     config.channels          = ss->channels;
158     config.rate              = ss->rate;
159     config.period_size       = period_size;
160     config.period_count      = period_count;
161     config.format            = ss->format;
162     config.start_threshold   = period_size;
163     config.stop_threshold    = 0xFFFFFFFF;
164     config.silence_threshold = 0;
165
166     AUDIO_LOG_INFO("card %s, device %s, direction %d, channels %d, rate %d, format %d, period_size %d, period_count %d",
167         card, device, direction, ss->channels, ss->rate, ss->format, period_size, period_count);
168
169     if (__parse_card_device_number(card, device, &card_u, &device_u) < 0) {
170         AUDIO_LOG_ERROR("Failed to get card device number from %s", device);
171         return NULL;
172     }
173
174     pcm = pcm_open(card_u, device_u, (direction == AUDIO_DIRECTION_OUT) ? PCM_OUT : PCM_IN, &config);
175     if (!pcm || !pcm_is_ready(pcm)) {
176         AUDIO_LOG_ERROR("Unable to open device (%s)", pcm_get_error(pcm));
177         pcm_close(pcm);
178         return NULL;
179     }
180
181     return pcm;
182 }
183
184 static int __tinyalsa_pcm_recover(struct pcm *pcm, int err)
185 {
186     if (err > 0)
187         err = -err;
188     if (err == -EINTR)  /* nothing to do, continue */
189         return 0;
190     if (err == -EPIPE) {
191         AUDIO_LOG_INFO("XRUN occurred");
192         err = pcm_prepare(pcm);
193         if (err < 0) {
194             AUDIO_LOG_ERROR("Could not recover from XRUN occurred, prepare failed : %d", err);
195             return err;
196         }
197         return 0;
198     }
199     if (err == -ESTRPIPE) {
200         /* tinyalsa does not support pcm resume, dont't care suspend case */
201         AUDIO_LOG_ERROR("Could not recover from suspend : %d", err);
202         return err;
203     }
204     return err;
205 }
206 #endif
207
208 audio_return_e _fmradio_pcm_open(audio_hal_s *ah)
209 {
210     audio_return_e audio_ret = AUDIO_RET_OK;
211     int ret = 0;
212     const char *device_name = NULL;
213     audio_pcm_sample_spec_s sample_spec;
214     uint8_t use_mmap = 0;
215     sample_spec.rate = 44100;
216     sample_spec.channels = 2;
217     sample_spec.format = SND_PCM_FORMAT_S16_LE;
218
219     if ((audio_ret = _ucm_get_device_name(ah, AUDIO_USE_CASE_VERB_FMRADIO, AUDIO_DIRECTION_OUT, &device_name)))
220         goto error_exit;
221
222 #ifdef __USE_TINYALSA__
223     AUDIO_LOG_WARN("need implementation for tinyAlsa");
224     return AUDIO_ERR_NOT_IMPLEMENTED;
225 #else
226     if (device_name) {
227         if((ret = snd_pcm_open((snd_pcm_t **)&ah->device.fmradio_pcm_out, (char *)device_name, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
228             AUDIO_LOG_ERROR("[%s] out pcm_open failed", device_name);
229             audio_ret = AUDIO_ERR_IOCTL;
230             goto error_exit;
231         }
232         AUDIO_LOG_INFO("[%s] out pcm_open success:%p", device_name, ah->device.fmradio_pcm_out);
233
234         if ((audio_ret = _pcm_set_hw_params(ah->device.fmradio_pcm_out, &sample_spec, &use_mmap, NULL, NULL)) < 0) {
235             AUDIO_LOG_ERROR("[%s] out __set_pcm_hw_params failed", device_name);
236             if ((audio_ret = _pcm_close(ah->device.pcm_out)))
237                 AUDIO_LOG_ERROR("failed to _pcm_close(), ret(0x%x)", audio_ret);
238             ah->device.fmradio_pcm_out = NULL;
239             goto error_exit;
240         }
241     }
242 #endif
243
244 error_exit:
245     if (device_name)
246         free((void*)device_name);
247
248     return audio_ret;
249 }
250
251 audio_return_e _fmradio_pcm_close(audio_hal_s *ah)
252 {
253     audio_return_e audio_ret = AUDIO_RET_OK;
254
255     AUDIO_RETURN_VAL_IF_FAIL(ah, AUDIO_ERR_PARAMETER);
256
257     AUDIO_LOG_INFO("close fmradio pcm handles");
258
259     if (ah->device.fmradio_pcm_out) {
260         if ((audio_ret = _pcm_close(ah->device.fmradio_pcm_out)))
261             AUDIO_LOG_ERROR("failed to _fmradio_pcm_close() for pcm_out, ret(0x%x)", audio_ret);
262         else {
263             ah->device.fmradio_pcm_out = NULL;
264             AUDIO_LOG_INFO("fmradio pcm_out handle close success");
265         }
266     }
267
268     return audio_ret;
269 }
270
271 audio_return_e _voice_pcm_open(audio_hal_s *ah)
272 {
273     int err, ret = 0;
274
275     AUDIO_RETURN_VAL_IF_FAIL(ah, AUDIO_ERR_PARAMETER);
276
277 #ifdef __USE_TINYALSA__
278     AUDIO_LOG_WARN("need implementation for tinyAlsa");
279     return AUDIO_ERR_NOT_IMPLEMENTED;
280 #else
281     AUDIO_LOG_INFO("open voice pcm handles");
282
283     /* Get playback voice-pcm from ucm conf. Open and set-params */
284     if ((err = snd_pcm_open((snd_pcm_t **)&ah->device.pcm_out, VOICE_PCM_DEVICE, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
285         AUDIO_LOG_ERROR("snd_pcm_open for %s failed. %s", VOICE_PCM_DEVICE, snd_strerror(err));
286         return AUDIO_ERR_IOCTL;
287     }
288     ret = __voice_pcm_set_params(ah, ah->device.pcm_out);
289
290     AUDIO_LOG_INFO("pcm playback device open success device(%s)", VOICE_PCM_DEVICE);
291
292     /* Get capture voice-pcm from ucm conf. Open and set-params */
293     if ((err = snd_pcm_open((snd_pcm_t **)&ah->device.pcm_in, VOICE_PCM_DEVICE, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
294         AUDIO_LOG_ERROR("snd_pcm_open for %s failed. %s", VOICE_PCM_DEVICE, snd_strerror(err));
295         return AUDIO_ERR_IOCTL;
296     }
297     ret = __voice_pcm_set_params(ah, ah->device.pcm_in);
298     AUDIO_LOG_INFO("pcm captures device open success device(%s)", VOICE_PCM_DEVICE);
299 #endif
300
301     return (ret == 0 ? AUDIO_RET_OK : AUDIO_ERR_INTERNAL);
302 }
303
304 audio_return_e _voice_pcm_close(audio_hal_s *ah, uint32_t direction)
305 {
306     audio_return_e audio_ret = AUDIO_RET_OK;
307
308     AUDIO_RETURN_VAL_IF_FAIL(ah, AUDIO_ERR_PARAMETER);
309
310     AUDIO_LOG_INFO("close voice pcm handles");
311
312     if (ah->device.pcm_out && (direction == AUDIO_DIRECTION_OUT)) {
313         if ((audio_ret = _pcm_close(ah->device.pcm_out)))
314             AUDIO_LOG_ERROR("failed to _pcm_close() for pcm_out, ret(0x%x)", audio_ret);
315         else {
316             ah->device.pcm_out = NULL;
317             AUDIO_LOG_INFO("voice pcm_out handle close success");
318         }
319     } else if (ah->device.pcm_in && (direction == AUDIO_DIRECTION_IN)) {
320         if ((audio_ret = _pcm_close(ah->device.pcm_in)))
321             AUDIO_LOG_ERROR("failed to _pcm_close() for pcm_in, ret(0x%x)", audio_ret);
322         else {
323             ah->device.pcm_in = NULL;
324             AUDIO_LOG_INFO("voice pcm_in handle close success");
325         }
326     }
327
328     return audio_ret;
329 }
330
331 audio_return_e _reset_pcm_devices(audio_hal_s *ah)
332 {
333     audio_return_e audio_ret = AUDIO_RET_OK;
334
335     AUDIO_RETURN_VAL_IF_FAIL(ah, AUDIO_ERR_PARAMETER);
336
337     if (ah->device.pcm_out) {
338         if (!(audio_ret = _pcm_close(ah->device.pcm_out))) {
339             ah->device.pcm_out = NULL;
340             AUDIO_LOG_INFO("pcm_out handle close success");
341         }
342     }
343     if (ah->device.pcm_in) {
344         if (!(audio_ret = _pcm_close(ah->device.pcm_in))) {
345             ah->device.pcm_in = NULL;
346             AUDIO_LOG_INFO("pcm_in handle close success");
347         }
348     }
349     if (ah->device.fmradio_pcm_out) {
350         if (!(audio_ret = _pcm_close(ah->device.fmradio_pcm_out))) {
351             ah->device.fmradio_pcm_out = NULL;
352             AUDIO_LOG_INFO("fmradio_pcm_out handle close success");
353         }
354     }
355
356     return audio_ret;
357 }
358
359 #ifndef __USE_TINYALSA__
360 static int __make_alsa_device_name(const char *card, const char *device, char device_name[])
361 {
362     AUDIO_RETURN_VAL_IF_FAIL(card, AUDIO_ERR_PARAMETER);
363     AUDIO_RETURN_VAL_IF_FAIL(device, AUDIO_ERR_PARAMETER);
364     AUDIO_RETURN_VAL_IF_FAIL(device_name, AUDIO_ERR_PARAMETER);
365
366     snprintf(device_name, DEVICE_NAME_MAX, "hw:%s,%s", card, device);
367     return 0;
368 }
369 #endif
370
371 audio_return_e _pcm_open(const char *card, const char *device, uint32_t direction, void *sample_spec,
372         uint32_t period_size, uint32_t periods, void **pcm_handle)
373 {
374     int err;
375
376     AUDIO_RETURN_VAL_IF_FAIL(card, AUDIO_ERR_PARAMETER);
377     AUDIO_RETURN_VAL_IF_FAIL(device, AUDIO_ERR_PARAMETER);
378     AUDIO_RETURN_VAL_IF_FAIL((direction == AUDIO_DIRECTION_OUT) || (direction == AUDIO_DIRECTION_IN),
379                             AUDIO_ERR_PARAMETER);
380
381     AUDIO_LOG_INFO("card(%s) device(%s) direction(%u) period_size(%u) periods(%u)",
382                     card, device, direction, period_size, periods);
383 #ifdef __USE_TINYALSA__
384     audio_pcm_sample_spec_s *ss;
385
386     ss = (audio_pcm_sample_spec_s *)sample_spec;
387     ss->format = __convert_format((audio_sample_format_s)ss->format);
388
389     *pcm_handle = __tinyalsa_open_device(card, device, ss, (size_t)period_size, (size_t)periods, direction);
390     if (*pcm_handle == NULL) {
391         AUDIO_LOG_ERROR("Error opening PCM device");
392         return AUDIO_ERR_RESOURCE;
393     }
394
395     if ((err = pcm_prepare((struct pcm *)*pcm_handle)) != 0) {
396         AUDIO_LOG_ERROR("Error prepare PCM device : %d", err);
397     }
398
399 #else  /* alsa-lib */
400     int mode;
401     audio_return_e ret;
402     char device_name[DEVICE_NAME_MAX];
403
404     __make_alsa_device_name(card, device, device_name);
405     mode =  SND_PCM_NONBLOCK | SND_PCM_NO_AUTO_RESAMPLE | SND_PCM_NO_AUTO_CHANNELS | SND_PCM_NO_AUTO_FORMAT;
406
407     if ((err = snd_pcm_open((snd_pcm_t **)pcm_handle, device_name, (direction == AUDIO_DIRECTION_OUT) ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE, mode)) < 0) {
408         AUDIO_LOG_ERROR("Error opening PCM device %s : %s", device_name, snd_strerror(err));
409         return AUDIO_ERR_RESOURCE;
410     }
411
412     if ((ret = _pcm_set_params(*pcm_handle, direction, sample_spec, period_size, periods)) != AUDIO_RET_OK) {
413         AUDIO_LOG_ERROR("Failed to set pcm parameters : %d", ret);
414         return ret;
415     }
416
417     AUDIO_LOG_INFO("PCM device %s", device_name);
418 #endif
419
420     return AUDIO_RET_OK;
421 }
422
423 audio_return_e _pcm_start(void *pcm_handle)
424 {
425     int err;
426
427 #ifdef __USE_TINYALSA__
428     if ((err = pcm_start(pcm_handle)) < 0) {
429         AUDIO_LOG_ERROR("Error starting PCM handle : %d", err);
430         return AUDIO_ERR_RESOURCE;
431     }
432 #else  /* alsa-lib */
433     if ((err = snd_pcm_start(pcm_handle)) < 0) {
434         AUDIO_LOG_ERROR("Error starting PCM handle : %s", snd_strerror(err));
435         return AUDIO_ERR_RESOURCE;
436     }
437 #endif
438
439     AUDIO_LOG_INFO("PCM handle %p start", pcm_handle);
440     return AUDIO_RET_OK;
441 }
442
443 audio_return_e _pcm_stop(void *pcm_handle)
444 {
445     int err;
446
447 #ifdef __USE_TINYALSA__
448     if ((err = pcm_stop(pcm_handle)) < 0) {
449         AUDIO_LOG_ERROR("Error stopping PCM handle : %d", err);
450         return AUDIO_ERR_RESOURCE;
451     }
452 #else  /* alsa-lib */
453     if ((err = snd_pcm_drop(pcm_handle)) < 0) {
454         AUDIO_LOG_ERROR("Error stopping PCM handle : %s", snd_strerror(err));
455         return AUDIO_ERR_RESOURCE;
456     }
457 #endif
458
459     AUDIO_LOG_INFO("PCM handle %p stop", pcm_handle);
460     return AUDIO_RET_OK;
461 }
462
463 audio_return_e _pcm_close(void *pcm_handle)
464 {
465     int err;
466
467     AUDIO_LOG_INFO("Try to close PCM handle %p", pcm_handle);
468
469 #ifdef __USE_TINYALSA__
470     if ((err = pcm_close(pcm_handle)) < 0) {
471         AUDIO_LOG_ERROR("Error closing PCM handle : %d", err);
472         return AUDIO_ERR_RESOURCE;
473     }
474 #else  /* alsa-lib */
475     if ((err = snd_pcm_close(pcm_handle)) < 0) {
476         AUDIO_LOG_ERROR("Error closing PCM handle : %s", snd_strerror(err));
477         return AUDIO_ERR_RESOURCE;
478     }
479 #endif
480
481     return AUDIO_RET_OK;
482 }
483
484 audio_return_e _pcm_avail(void *pcm_handle, uint32_t *avail)
485 {
486 #ifdef __USE_TINYALSA__
487     struct timespec tspec;
488     unsigned int frames_avail = 0;
489     int err;
490
491     err = pcm_get_htimestamp(pcm_handle, &frames_avail, &tspec);
492     if (err < 0) {
493         AUDIO_LOG_ERROR("Could not get avail and timespec at PCM handle %p : %d", pcm_handle, err);
494         return AUDIO_ERR_IOCTL;
495     }
496
497 #ifdef DEBUG_TIMING
498     AUDIO_LOG_DEBUG("avail = %d", frames_avail);
499 #endif
500
501     *avail = (uint32_t)frames_avail;
502 #else  /* alsa-lib */
503     snd_pcm_sframes_t frames_avail;
504
505     if ((frames_avail = snd_pcm_avail(pcm_handle)) < 0) {
506         AUDIO_LOG_ERROR("Could not get avail at PCM handle %p : %ld", pcm_handle, frames_avail);
507         return AUDIO_ERR_IOCTL;
508     }
509
510 #ifdef DEBUG_TIMING
511     AUDIO_LOG_DEBUG("avail = %d", frames_avail);
512 #endif
513
514     *avail = (uint32_t)frames_avail;
515 #endif
516
517     return AUDIO_RET_OK;
518 }
519
520 audio_return_e _pcm_write(void *pcm_handle, const void *buffer, uint32_t frames)
521 {
522 #ifdef __USE_TINYALSA__
523     int err;
524
525     err = pcm_write(pcm_handle, buffer, pcm_frames_to_bytes(pcm_handle, (unsigned int)frames));
526     if (err < 0) {
527         AUDIO_LOG_ERROR("Failed to write pcm : %d", err);
528         return AUDIO_ERR_IOCTL;
529     }
530
531 #ifdef DEBUG_TIMING
532     AUDIO_LOG_DEBUG("_pcm_write = %d", frames);
533 #endif
534 #else  /* alsa-lib */
535     snd_pcm_sframes_t frames_written;
536
537     AUDIO_RETURN_VAL_IF_FAIL(pcm_handle, AUDIO_ERR_PARAMETER);
538
539     frames_written = snd_pcm_writei(pcm_handle, buffer, (snd_pcm_uframes_t) frames);
540     if (frames_written < 0) {
541         AUDIO_LOG_ERROR("Failed to write pcm : %ld", frames_written);
542         return AUDIO_ERR_IOCTL;
543     }
544
545 #ifdef DEBUG_TIMING
546     AUDIO_LOG_DEBUG("_pcm_write = (%d / %d)", frames_written, frames);
547 #endif
548 #endif
549
550     return AUDIO_RET_OK;
551 }
552
553 audio_return_e _pcm_read(void *pcm_handle, void *buffer, uint32_t frames)
554 {
555 #ifdef __USE_TINYALSA__
556     int err;
557
558     err = pcm_read(pcm_handle, buffer, pcm_frames_to_bytes(pcm_handle, (unsigned int)frames));
559     if (err < 0) {
560         AUDIO_LOG_ERROR("Failed to read pcm : %d", err);
561         return AUDIO_ERR_IOCTL;
562     }
563
564 #ifdef DEBUG_TIMING
565     AUDIO_LOG_DEBUG("audio_pcm_read = %d", frames);
566 #endif
567 #else  /* alsa-lib */
568     snd_pcm_sframes_t frames_read;
569
570     frames_read = snd_pcm_readi(pcm_handle, buffer, (snd_pcm_uframes_t)frames);
571     if (frames_read < 0) {
572         AUDIO_LOG_ERROR("Failed to read pcm : %ld", frames_read);
573         return AUDIO_ERR_IOCTL;
574     }
575
576 #ifdef DEBUG_TIMING
577     AUDIO_LOG_DEBUG("_pcm_read = (%d / %d)", frames_read, frames);
578 #endif
579 #endif
580
581     return AUDIO_RET_OK;
582 }
583
584 audio_return_e _pcm_get_fd(void *pcm_handle, int *fd)
585 {
586     /* we use an internal API of the (tiny)alsa library, so it causes warning message during compile */
587 #ifdef __USE_TINYALSA__
588     *fd = _pcm_poll_descriptor((struct pcm *)pcm_handle);
589 #else  /* alsa-lib */
590     *fd = _snd_pcm_poll_descriptor((snd_pcm_t *)pcm_handle);
591 #endif
592     return AUDIO_RET_OK;
593 }
594
595 audio_return_e _pcm_recover(void *pcm_handle, int revents)
596 {
597     int state, err;
598
599     AUDIO_RETURN_VAL_IF_FAIL(pcm_handle, AUDIO_ERR_PARAMETER);
600
601     if (revents & POLLERR)
602         AUDIO_LOG_DEBUG("Got POLLERR from ALSA");
603     if (revents & POLLNVAL)
604         AUDIO_LOG_DEBUG("Got POLLNVAL from ALSA");
605     if (revents & POLLHUP)
606         AUDIO_LOG_DEBUG("Got POLLHUP from ALSA");
607     if (revents & POLLPRI)
608         AUDIO_LOG_DEBUG("Got POLLPRI from ALSA");
609     if (revents & POLLIN)
610         AUDIO_LOG_DEBUG("Got POLLIN from ALSA");
611     if (revents & POLLOUT)
612         AUDIO_LOG_DEBUG("Got POLLOUT from ALSA");
613
614 #ifdef __USE_TINYALSA__
615     state = pcm_state(pcm_handle);
616     AUDIO_LOG_DEBUG("PCM state is %d", state);
617
618     switch (state) {
619         case PCM_STATE_XRUN:
620             if ((err = __tinyalsa_pcm_recover(pcm_handle, -EPIPE)) != 0) {
621                 AUDIO_LOG_ERROR("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN : %d", err);
622                 return AUDIO_ERR_IOCTL;
623             }
624             break;
625
626         case PCM_STATE_SUSPENDED:
627             if ((err = __tinyalsa_pcm_recover(pcm_handle, -ESTRPIPE)) != 0) {
628                 AUDIO_LOG_ERROR("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED : %d", err);
629                 return AUDIO_ERR_IOCTL;
630             }
631             break;
632
633         default:
634             pcm_stop(pcm_handle);
635             if ((err = pcm_prepare(pcm_handle)) < 0) {
636                 AUDIO_LOG_ERROR("Could not recover from POLLERR|POLLNVAL|POLLHUP with pcm_prepare() : %d", err);
637                 return AUDIO_ERR_IOCTL;
638             }
639     }
640 #else  /* alsa-lib */
641     state = snd_pcm_state(pcm_handle);
642     AUDIO_LOG_DEBUG("PCM state is %s", snd_pcm_state_name(state));
643
644     /* Try to recover from this error */
645
646     switch (state) {
647         case SND_PCM_STATE_XRUN:
648             if ((err = snd_pcm_recover(pcm_handle, -EPIPE, 1)) != 0) {
649                 AUDIO_LOG_ERROR("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN : %d", err);
650                 return AUDIO_ERR_IOCTL;
651             }
652             break;
653
654         case SND_PCM_STATE_SUSPENDED:
655             if ((err = snd_pcm_recover(pcm_handle, -ESTRPIPE, 1)) != 0) {
656                 AUDIO_LOG_ERROR("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED : %d", err);
657                 return AUDIO_ERR_IOCTL;
658             }
659             break;
660
661         default:
662             snd_pcm_drop(pcm_handle);
663             if ((err = snd_pcm_prepare(pcm_handle)) < 0) {
664                 AUDIO_LOG_ERROR("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare() : %d", err);
665                 return AUDIO_ERR_IOCTL;
666             }
667             break;
668     }
669 #endif
670
671     AUDIO_LOG_DEBUG("_pcm_recover");
672     return AUDIO_RET_OK;
673 }
674
675 audio_return_e _pcm_get_params(void *pcm_handle, uint32_t direction, void *sample_spec, uint32_t *period_size, uint32_t *periods)
676 {
677 #ifdef __USE_TINYALSA__
678     audio_pcm_sample_spec_s *ss;
679     unsigned int _period_size, _buffer_size, _periods, _format, _rate, _channels;
680     unsigned int _start_threshold, _stop_threshold, _silence_threshold;
681     struct pcm_config *config;
682
683     ss = (audio_pcm_sample_spec_s *)sample_spec;
684
685     /* we use an internal API of the tiny alsa library, so it causes warning message during compile */
686     _pcm_config(pcm_handle, &config);
687
688     *period_size = config->period_size;
689     *periods     = config->period_count;
690     _buffer_size = config->period_size * config->period_count;
691     ss->format   = config->format;
692     ss->rate     = config->rate;
693     ss->channels = config->channels;
694     _start_threshold   = config->start_threshold;
695     _stop_threshold    = config->stop_threshold;
696     _silence_threshold = config->silence_threshold;
697
698     AUDIO_LOG_DEBUG("_pcm_get_params (handle %p, format %d, rate %u, channels %u, period_size %u, periods %u, buffer_size %u)",
699                     pcm_handle, config->format, config->rate, config->channels, config->period_size, config->period_count, _buffer_size);
700 #else  /* alsa-lib */
701     int err;
702     audio_pcm_sample_spec_s *ss;
703     int dir;
704     snd_pcm_uframes_t _period_size, _buffer_size;
705     snd_pcm_format_t _format;
706     unsigned int _rate, _channels;
707     snd_pcm_uframes_t _start_threshold, _stop_threshold, _silence_threshold, _avail_min;
708     unsigned int _periods;
709     snd_pcm_hw_params_t *hwparams;
710     snd_pcm_sw_params_t *swparams;
711
712     ss = (audio_pcm_sample_spec_s *)sample_spec;
713
714     snd_pcm_hw_params_alloca(&hwparams);
715     snd_pcm_sw_params_alloca(&swparams);
716
717     if ((err = snd_pcm_hw_params_current(pcm_handle, hwparams)) < 0) {
718         AUDIO_LOG_ERROR("snd_pcm_hw_params_current() failed : %d", err);
719         return AUDIO_ERR_PARAMETER;
720     }
721
722     if ((err = snd_pcm_hw_params_get_period_size(hwparams, &_period_size, &dir)) < 0 ||
723         (err = snd_pcm_hw_params_get_buffer_size(hwparams, &_buffer_size)) < 0 ||
724         (err = snd_pcm_hw_params_get_periods(hwparams, &_periods, &dir)) < 0 ||
725         (err = snd_pcm_hw_params_get_format(hwparams, &_format)) < 0 ||
726         (err = snd_pcm_hw_params_get_rate(hwparams, &_rate, &dir)) < 0 ||
727         (err = snd_pcm_hw_params_get_channels(hwparams, &_channels)) < 0) {
728         AUDIO_LOG_ERROR("snd_pcm_hw_params_get_{period_size|buffer_size|periods|format|rate|channels}() failed : %d", err);
729         return AUDIO_ERR_PARAMETER;
730     }
731
732     *period_size = _period_size;
733     *periods     = _periods;
734     ss->format   = _format;
735     ss->rate     = _rate;
736     ss->channels = _channels;
737
738     if ((err = snd_pcm_sw_params_current(pcm_handle, swparams)) < 0) {
739         AUDIO_LOG_ERROR("snd_pcm_sw_params_current() failed : %d", err);
740         return AUDIO_ERR_PARAMETER;
741     }
742
743     if ((err = snd_pcm_sw_params_get_start_threshold(swparams, &_start_threshold)) < 0  ||
744         (err = snd_pcm_sw_params_get_stop_threshold(swparams, &_stop_threshold)) < 0  ||
745         (err = snd_pcm_sw_params_get_silence_threshold(swparams, &_silence_threshold)) < 0  ||
746         (err = snd_pcm_sw_params_get_avail_min(swparams, &_avail_min)) < 0) {
747         AUDIO_LOG_ERROR("snd_pcm_sw_params_get_{start_threshold|stop_threshold|silence_threshold|avail_min}() failed : %d", err);
748     }
749
750     AUDIO_LOG_DEBUG("_pcm_get_params (handle %p, format %d, rate %u, channels %u, period_size %lu, periods %u, buffer_size %lu)",
751                     pcm_handle, _format, _rate, _channels, _period_size, _periods, _buffer_size);
752 #endif
753
754     return AUDIO_RET_OK;
755 }
756
757 audio_return_e _pcm_set_params(void *pcm_handle, uint32_t direction, void *sample_spec, uint32_t period_size, uint32_t periods)
758 {
759 #ifdef __USE_TINYALSA__
760     /* Parameters are only acceptable in pcm_open() function */
761     AUDIO_LOG_DEBUG("_pcm_set_params");
762 #else  /* alsa-lib */
763     int err;
764     audio_pcm_sample_spec_s ss;
765     snd_pcm_uframes_t _buffer_size;
766     snd_pcm_hw_params_t *hwparams;
767     snd_pcm_sw_params_t *swparams;
768
769     ss = *(audio_pcm_sample_spec_s *)sample_spec;
770
771     snd_pcm_hw_params_alloca(&hwparams);
772     snd_pcm_sw_params_alloca(&swparams);
773
774     /* Set hw params */
775     if ((err = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) {
776         AUDIO_LOG_ERROR("snd_pcm_hw_params_any() failed : %d", err);
777         return AUDIO_ERR_PARAMETER;
778     }
779
780     if ((err = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) {
781         AUDIO_LOG_ERROR("snd_pcm_hw_params_set_rate_resample() failed : %d", err);
782         return AUDIO_ERR_PARAMETER;
783     }
784
785     if ((err = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
786         AUDIO_LOG_ERROR("snd_pcm_hw_params_set_access() failed : %d", err);
787         return AUDIO_ERR_PARAMETER;
788     }
789
790     ss.format = __convert_format((audio_sample_format_s)ss.format);
791     if ((err = snd_pcm_hw_params_set_format(pcm_handle, hwparams, ss.format)) < 0) {
792         AUDIO_LOG_ERROR("snd_pcm_hw_params_set_format() failed : %d", err);
793         return AUDIO_ERR_PARAMETER;
794     }
795
796     if ((err = snd_pcm_hw_params_set_rate(pcm_handle, hwparams, ss.rate, 0)) < 0) {
797         AUDIO_LOG_ERROR("snd_pcm_hw_params_set_rate() failed : %d", err);
798         return AUDIO_ERR_PARAMETER;
799     }
800
801     if ((err = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, ss.channels)) < 0) {
802         AUDIO_LOG_ERROR("snd_pcm_hw_params_set_channels(%u) failed : %d", ss.channels, err);
803         return AUDIO_ERR_PARAMETER;
804     }
805
806     if ((err = snd_pcm_hw_params_set_period_size(pcm_handle, hwparams, period_size, 0)) < 0) {
807         AUDIO_LOG_ERROR("snd_pcm_hw_params_set_period_size(%u) failed : %d", period_size, err);
808         return AUDIO_ERR_PARAMETER;
809     }
810
811     if ((err = snd_pcm_hw_params_set_periods(pcm_handle, hwparams, periods, 0)) < 0) {
812         AUDIO_LOG_ERROR("snd_pcm_hw_params_set_periods(%u) failed : %d", periods, err);
813         return AUDIO_ERR_PARAMETER;
814     }
815
816     _buffer_size = period_size * periods;
817     if ((err = snd_pcm_hw_params_set_buffer_size(pcm_handle, hwparams, _buffer_size)) < 0) {
818         AUDIO_LOG_ERROR("snd_pcm_hw_params_set_buffer_size(%lu) failed : %d", _buffer_size, err);
819         return AUDIO_ERR_PARAMETER;
820     }
821
822     if ((err = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) {
823         AUDIO_LOG_ERROR("snd_pcm_hw_params failed : %d", err);
824         return AUDIO_ERR_IOCTL;
825     }
826
827     /* Set sw params */
828     if ((err = snd_pcm_sw_params_current(pcm_handle, swparams)) < 0) {
829         AUDIO_LOG_ERROR("Unable to determine current swparams : %d", err);
830         return AUDIO_ERR_PARAMETER;
831     }
832
833     if ((err = snd_pcm_sw_params_set_tstamp_mode(pcm_handle, swparams, SND_PCM_TSTAMP_ENABLE)) < 0) {
834         AUDIO_LOG_ERROR("Unable to enable time stamping : %d", err);
835         return AUDIO_ERR_PARAMETER;
836     }
837
838     if ((err = snd_pcm_sw_params_set_stop_threshold(pcm_handle, swparams, 0xFFFFFFFF)) < 0) {
839         AUDIO_LOG_ERROR("Unable to set stop threshold : %d", err);
840         return AUDIO_ERR_PARAMETER;
841     }
842
843     if ((err = snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, period_size / 2)) < 0) {
844         AUDIO_LOG_ERROR("Unable to set start threshold : %d", err);
845         return AUDIO_ERR_PARAMETER;
846     }
847
848     if ((err = snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, 1024)) < 0) {
849         AUDIO_LOG_ERROR("snd_pcm_sw_params_set_avail_min() failed : %d", err);
850         return AUDIO_ERR_PARAMETER;
851     }
852
853     if ((err = snd_pcm_sw_params(pcm_handle, swparams)) < 0) {
854         AUDIO_LOG_ERROR("Unable to set sw params : %d", err);
855         return AUDIO_ERR_IOCTL;
856     }
857
858     /* Prepare device */
859     if ((err = snd_pcm_prepare(pcm_handle)) < 0) {
860         AUDIO_LOG_ERROR("snd_pcm_prepare() failed : %d", err);
861         return AUDIO_ERR_IOCTL;
862     }
863
864     AUDIO_LOG_DEBUG("_pcm_set_params (handle %p, format %d, rate %u, channels %u, period_size %u, periods %u, buffer_size %lu)",
865                     pcm_handle, ss.format, ss.rate, ss.channels, period_size, periods, _buffer_size);
866 #endif
867
868     return AUDIO_RET_OK;
869 }
870
871 /* Generic snd pcm interface APIs */
872 audio_return_e _pcm_set_hw_params(snd_pcm_t *pcm, audio_pcm_sample_spec_s *sample_spec, uint8_t *use_mmap, snd_pcm_uframes_t *period_size, snd_pcm_uframes_t *buffer_size)
873 {
874     audio_return_e ret = AUDIO_RET_OK;
875     snd_pcm_hw_params_t *hwparams;
876     int err = 0;
877     int dir;
878     unsigned int val = 0;
879     snd_pcm_uframes_t _period_size = period_size ? *period_size : 0;
880     snd_pcm_uframes_t _buffer_size = buffer_size ? *buffer_size : 0;
881     uint8_t _use_mmap = use_mmap && *use_mmap;
882     uint32_t channels = 0;
883
884     AUDIO_RETURN_VAL_IF_FAIL(pcm, AUDIO_ERR_PARAMETER);
885
886     snd_pcm_hw_params_alloca(&hwparams);
887
888     /* Skip parameter setting to null device. */
889     if (snd_pcm_type(pcm) == SND_PCM_TYPE_NULL)
890         return AUDIO_ERR_IOCTL;
891
892     /* Allocate a hardware parameters object. */
893     snd_pcm_hw_params_alloca(&hwparams);
894
895     /* Fill it in with default values. */
896     if (snd_pcm_hw_params_any(pcm, hwparams) < 0) {
897         AUDIO_LOG_ERROR("snd_pcm_hw_params_any() : failed! - %s\n", snd_strerror(err));
898         goto error;
899     }
900
901     /* Set the desired hardware parameters. */
902
903     if (_use_mmap) {
904
905         if (snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) {
906
907             /* mmap() didn't work, fall back to interleaved */
908
909             if ((ret = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
910                 AUDIO_LOG_DEBUG("snd_pcm_hw_params_set_access() failed: %s", snd_strerror(ret));
911                 goto error;
912             }
913
914             _use_mmap = 0;
915         }
916
917     } else if ((ret = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
918         AUDIO_LOG_DEBUG("snd_pcm_hw_params_set_access() failed: %s", snd_strerror(ret));
919         goto error;
920     }
921     AUDIO_LOG_DEBUG("setting rate - %d", sample_spec->rate);
922     err = snd_pcm_hw_params_set_rate(pcm, hwparams, sample_spec->rate, 0);
923     if (err < 0) {
924         AUDIO_LOG_ERROR("snd_pcm_hw_params_set_rate() : failed! - %s\n", snd_strerror(err));
925     }
926
927     err = snd_pcm_hw_params(pcm, hwparams);
928     if (err < 0) {
929         AUDIO_LOG_ERROR("snd_pcm_hw_params() : failed! - %s\n", snd_strerror(err));
930         goto error;
931     }
932
933     /* Dump current param */
934
935     if ((ret = snd_pcm_hw_params_current(pcm, hwparams)) < 0) {
936         AUDIO_LOG_INFO("snd_pcm_hw_params_current() failed: %s", snd_strerror(ret));
937         goto error;
938     }
939
940     if ((ret = snd_pcm_hw_params_get_period_size(hwparams, &_period_size, &dir)) < 0 ||
941         (ret = snd_pcm_hw_params_get_buffer_size(hwparams, &_buffer_size)) < 0) {
942         AUDIO_LOG_INFO("snd_pcm_hw_params_get_{period|buffer}_size() failed: %s", snd_strerror(ret));
943         goto error;
944     }
945
946     snd_pcm_hw_params_get_access(hwparams, (snd_pcm_access_t *) &val);
947     AUDIO_LOG_DEBUG("access type = %s\n", snd_pcm_access_name((snd_pcm_access_t)val));
948
949     snd_pcm_hw_params_get_format(hwparams, &sample_spec->format);
950     AUDIO_LOG_DEBUG("format = '%s' (%s)\n",
951                     snd_pcm_format_name((snd_pcm_format_t)sample_spec->format),
952                     snd_pcm_format_description((snd_pcm_format_t)sample_spec->format));
953
954     snd_pcm_hw_params_get_subformat(hwparams, (snd_pcm_subformat_t *)&val);
955     AUDIO_LOG_DEBUG("subformat = '%s' (%s)\n",
956                     snd_pcm_subformat_name((snd_pcm_subformat_t)val),
957                     snd_pcm_subformat_description((snd_pcm_subformat_t)val));
958
959     snd_pcm_hw_params_get_channels(hwparams, &channels);
960     sample_spec->channels = (uint8_t)channels;
961     AUDIO_LOG_DEBUG("channels = %d\n", sample_spec->channels);
962
963     if (buffer_size)
964         *buffer_size = _buffer_size;
965
966     if (period_size)
967         *period_size = _period_size;
968
969     if (use_mmap)
970         *use_mmap = _use_mmap;
971
972     return AUDIO_RET_OK;
973
974 error:
975     return AUDIO_ERR_RESOURCE;
976 }
977
978 audio_return_e _pcm_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min, uint8_t period_event)
979 {
980     snd_pcm_sw_params_t *swparams;
981     snd_pcm_uframes_t boundary;
982     int err;
983
984     AUDIO_RETURN_VAL_IF_FAIL(pcm, AUDIO_ERR_PARAMETER);
985
986     snd_pcm_sw_params_alloca(&swparams);
987
988     if ((err = snd_pcm_sw_params_current(pcm, swparams)) < 0) {
989         AUDIO_LOG_WARN("Unable to determine current swparams: %s\n", snd_strerror(err));
990         goto error;
991     }
992     if ((err = snd_pcm_sw_params_set_period_event(pcm, swparams, period_event)) < 0) {
993         AUDIO_LOG_WARN("Unable to disable period event: %s\n", snd_strerror(err));
994         goto error;
995     }
996     if ((err = snd_pcm_sw_params_set_tstamp_mode(pcm, swparams, SND_PCM_TSTAMP_ENABLE)) < 0) {
997         AUDIO_LOG_WARN("Unable to enable time stamping: %s\n", snd_strerror(err));
998         goto error;
999     }
1000     if ((err = snd_pcm_sw_params_get_boundary(swparams, &boundary)) < 0) {
1001         AUDIO_LOG_WARN("Unable to get boundary: %s\n", snd_strerror(err));
1002         goto error;
1003     }
1004     if ((err = snd_pcm_sw_params_set_stop_threshold(pcm, swparams, boundary)) < 0) {
1005         AUDIO_LOG_WARN("Unable to set stop threshold: %s\n", snd_strerror(err));
1006         goto error;
1007     }
1008     if ((err = snd_pcm_sw_params_set_start_threshold(pcm, swparams, (snd_pcm_uframes_t) avail_min)) < 0) {
1009         AUDIO_LOG_WARN("Unable to set start threshold: %s\n", snd_strerror(err));
1010         goto error;
1011     }
1012     if ((err = snd_pcm_sw_params_set_avail_min(pcm, swparams, avail_min)) < 0) {
1013         AUDIO_LOG_WARN("snd_pcm_sw_params_set_avail_min() failed: %s", snd_strerror(err));
1014         goto error;
1015     }
1016     if ((err = snd_pcm_sw_params(pcm, swparams)) < 0) {
1017         AUDIO_LOG_WARN("Unable to set sw params: %s\n", snd_strerror(err));
1018         goto error;
1019     }
1020     return AUDIO_RET_OK;
1021 error:
1022     return err;
1023 }