alsa: some stop handling
[libpicofe.git] / linux / sndout_alsa.c
1 /*
2  * (C) notaz, 2013
3  *
4  * This work is licensed under the terms of any of these licenses
5  * (at your option):
6  *  - GNU GPL, version 2 or later.
7  *  - GNU LGPL, version 2.1 or later.
8  *  - MAME license.
9  * See the COPYING file in the top-level directory.
10  */
11
12 #include <stdio.h>
13 #include <alsa/asoundlib.h>
14 #include <unistd.h>
15
16 #include "sndout_alsa.h"
17
18 #define PFX "sndout_alsa: "
19
20 static snd_pcm_t *handle;
21 static snd_pcm_uframes_t buffer_size, period_size;
22 static void *silent_period;
23 static unsigned int channels;
24 static int failure_counter;
25
26 int sndout_alsa_init(void)
27 {
28         int ret;
29
30         ret = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
31         if (ret != 0)
32                 return -1;
33
34         return 0;
35 }
36
37 int sndout_alsa_start(int rate_, int stereo)
38 {
39         snd_pcm_hw_params_t *hwparams = NULL;
40         unsigned int rate = rate_;
41         int samples, shift;
42         int ret;
43
44         samples = rate * 40 / 1000;
45         for (shift = 8; (1 << shift) < samples; shift++)
46                 ;
47         period_size = 1 << shift;
48         buffer_size = 8 * period_size;
49
50         snd_pcm_hw_params_alloca(&hwparams);
51
52         ret  = snd_pcm_hw_params_any(handle, hwparams);
53         ret |= snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
54         ret |= snd_pcm_hw_params_set_format(handle, hwparams, SND_PCM_FORMAT_S16_LE);
55         ret |= snd_pcm_hw_params_set_channels(handle, hwparams, stereo ? 2 : 1);
56         ret |= snd_pcm_hw_params_set_rate_near(handle, hwparams, &rate, 0);
57         ret |= snd_pcm_hw_params_set_buffer_size_near(handle, hwparams, &buffer_size);
58         ret |= snd_pcm_hw_params_set_period_size_near(handle, hwparams, &period_size, NULL);
59
60         if (ret != 0) {
61                 fprintf(stderr, PFX "failed to set hwparams\n");
62                 goto fail;
63         }
64
65         ret = snd_pcm_hw_params(handle, hwparams);
66         if (ret != 0) {
67                 fprintf(stderr, PFX "failed to apply hwparams: %d\n", ret);
68                 goto fail;
69         }
70
71         snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
72         snd_pcm_hw_params_get_period_size(hwparams, &period_size, NULL);
73         snd_pcm_hw_params_get_channels(hwparams, &channels);
74
75         silent_period = realloc(silent_period, period_size * 2 * channels);
76         if (silent_period != NULL)
77                 memset(silent_period, 0, period_size * 2 * channels);
78
79         ret = snd_pcm_prepare(handle);
80         if (ret != 0) {
81                 fprintf(stderr, PFX "snd_pcm_prepare failed: %d\n", ret);
82                 goto fail;
83         }
84
85         ret = snd_pcm_start(handle);
86         if (ret != 0) {
87                 fprintf(stderr, PFX "snd_pcm_start failed: %d\n", ret);
88                 goto fail;
89         }
90
91         failure_counter = 0;
92
93         return 0;
94
95 fail:
96         // to flush out redirected logs
97         fflush(stdout);
98         fflush(stderr);
99         return -1;
100 }
101
102 void sndout_alsa_stop(void)
103 {
104         int ret = snd_pcm_drop(handle);
105         if (ret != 0)
106                 fprintf(stderr, PFX "snd_pcm_drop failed: %d\n", ret);
107 }
108
109 void sndout_alsa_wait(void)
110 {
111         snd_pcm_sframes_t left;
112
113         while (1)
114         {
115                 left = snd_pcm_avail(handle);
116                 if (left < 0 || left >= buffer_size / 2)
117                         break;
118
119                 usleep(4000);
120         }
121 }
122
123 int sndout_alsa_write_nb(const void *samples, int len)
124 {
125         snd_pcm_sframes_t left;
126         int ret;
127
128         len /= 2;
129         if (channels == 2)
130                 len /= 2;
131
132         left = snd_pcm_avail(handle);
133         if (left >= 0 && left < len)
134                 return 0;
135
136         ret = snd_pcm_writei(handle, samples, len);
137         if (ret < 0) {
138                 ret = snd_pcm_recover(handle, ret, 1);
139                 if (ret != 0 && failure_counter++ < 5)
140                         fprintf(stderr, PFX "snd_pcm_recover: %d\n", ret);
141
142                 if (silent_period)
143                         snd_pcm_writei(handle, silent_period, period_size);
144                 snd_pcm_writei(handle, samples, len);
145         }
146
147         return len;
148 }
149
150 void sndout_alsa_exit(void)
151 {
152         snd_pcm_close(handle);
153         handle = NULL;
154 }