add preliminary alsa driver too
[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 static snd_pcm_t *handle;
19 static snd_pcm_uframes_t buffer_size, period_size;
20 static unsigned int channels;
21 static void *silent_period;
22
23 int sndout_alsa_init(void)
24 {
25         int ret;
26
27         ret = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
28         if (ret != 0)
29                 return -1;
30
31         return 0;
32 }
33
34 int sndout_alsa_start(int rate_, int stereo)
35 {
36         snd_pcm_hw_params_t *hwparams = NULL;
37         unsigned int rate = rate_;
38         int samples, shift;
39         int ret;
40
41         samples = rate * 40 / 1000;
42         for (shift = 8; (1 << shift) < samples; shift++)
43                 ;
44         period_size = 1 << shift;
45         buffer_size = 8 * period_size;
46
47         snd_pcm_hw_params_alloca(&hwparams);
48
49         ret  = snd_pcm_hw_params_any(handle, hwparams);
50         ret |= snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
51         ret |= snd_pcm_hw_params_set_format(handle, hwparams, SND_PCM_FORMAT_S16_LE);
52         ret |= snd_pcm_hw_params_set_channels(handle, hwparams, stereo ? 2 : 1);
53         ret |= snd_pcm_hw_params_set_rate_near(handle, hwparams, &rate, 0);
54         ret |= snd_pcm_hw_params_set_buffer_size_near(handle, hwparams, &buffer_size);
55         ret |= snd_pcm_hw_params_set_period_size_near(handle, hwparams, &period_size, NULL);
56
57         if (ret != 0) {
58                 fprintf(stderr, "sndout_alsa: failed to set hwparams\n");
59                 return -1;
60         }
61
62         ret = snd_pcm_hw_params(handle, hwparams);
63         if (ret != 0) {
64                 fprintf(stderr, "sndout_alsa: failed to apply hwparams: %d\n",
65                         ret);
66                 return -1;
67         }
68
69         snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size);
70         snd_pcm_hw_params_get_period_size(hwparams, &period_size, NULL);
71         snd_pcm_hw_params_get_channels(hwparams, &channels);
72
73         silent_period = realloc(silent_period, period_size * 2 * channels);
74         if (silent_period != NULL)
75                 memset(silent_period, 0, period_size * 2 * channels);
76
77         ret  = snd_pcm_prepare(handle);
78         ret |= snd_pcm_start(handle);
79
80         if (ret != 0) {
81                 fprintf(stderr, "sndout_alsa: failed to start pcm: %d\n",
82                         ret);
83                 return -1;
84         }
85
86         return 0;
87 }
88
89 void sndout_alsa_stop(void)
90 {
91         // nothing?
92 }
93
94 void sndout_alsa_wait(void)
95 {
96         snd_pcm_sframes_t left;
97
98         while (1)
99         {
100                 left = snd_pcm_avail(handle);
101                 if (left < 0 || left >= buffer_size / 2)
102                         break;
103
104                 usleep(4000);
105         }
106 }
107
108 int sndout_alsa_write_nb(const void *samples, int len)
109 {
110         snd_pcm_sframes_t left;
111         int ret;
112
113         len /= 2;
114         if (channels == 2)
115                 len /= 2;
116
117         left = snd_pcm_avail(handle);
118         if (left >= 0 && left < len)
119                 return 0;
120
121         ret = snd_pcm_writei(handle, samples, len);
122         if (ret < 0) {
123                 ret = snd_pcm_recover(handle, ret, 1);
124                 if (ret != 0)
125                         fprintf(stderr, "sndout_alsa snd_pcm_recover: %d\n", ret);
126
127                 if (silent_period)
128                         snd_pcm_writei(handle, silent_period, period_size);
129                 snd_pcm_writei(handle, samples, len);
130         }
131
132         return len;
133 }
134
135 void sndout_alsa_exit(void)
136 {
137         snd_pcm_close(handle);
138         handle = NULL;
139 }