source: roaraudio/roard/driver_alsa.c @ 6052:d48765b2475e

Last change on this file since 6052:d48765b2475e was 6052:d48765b2475e, checked in by phi, 9 years ago

updated copyright headers

File size: 11.6 KB
Line 
1//driver_alsa.c:
2
3/*
4 *      Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2008-2015
5 *      Copyright (C) Hans-Kristian 'maister' Arntzen - 2010
6 *
7 *  This file is part of roard a part of RoarAudio,
8 *  a cross-platform sound system for both, home and professional use.
9 *  See README for details.
10 *
11 *  This file is free software; you can redistribute it and/or modify
12 *  it under the terms of the GNU General Public License version 3
13 *  as published by the Free Software Foundation.
14 *
15 *  RoarAudio is distributed in the hope that it will be useful,
16 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 *  GNU General Public License for more details.
19 *
20 *  You should have received a copy of the GNU General Public License
21 *  along with this software; see the file COPYING.  If not, write to
22 *  the Free Software Foundation, 51 Franklin Street, Fifth Floor,
23 *  Boston, MA 02110-1301, USA.
24 *
25 */
26
27#include "roard.h"
28
29#ifdef ROAR_HAVE_LIBASOUND
30
31#define ALSA_PCM_NEW_HW_PARAMS_API
32
33static snd_pcm_format_t driver_alsa_roar_fmt_to_alsa(struct roar_audio_info * info) {
34 snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN;
35
36 switch (info->codec) {
37  case ROAR_CODEC_PCM_S_LE:
38    switch (info->bits) {
39     case  8: format = SND_PCM_FORMAT_S8;      break;
40     case 16: format = SND_PCM_FORMAT_S16_LE;  break;
41     case 24: format = SND_PCM_FORMAT_S24_3LE; break;
42     case 32: format = SND_PCM_FORMAT_S32_LE;  break;
43    }
44   break;
45  case ROAR_CODEC_PCM_S_BE:
46    switch (info->bits) {
47     case  8: format = SND_PCM_FORMAT_S8;      break;
48     case 16: format = SND_PCM_FORMAT_S16_BE;  break;
49     case 24: format = SND_PCM_FORMAT_S24_3BE; break;
50     case 32: format = SND_PCM_FORMAT_S32_BE;  break;
51    }
52   break;
53  case ROAR_CODEC_PCM_U_LE:
54    switch (info->bits) {
55     case  8: format = SND_PCM_FORMAT_U8;      break;
56     case 16: format = SND_PCM_FORMAT_U16_LE;  break;
57     case 24: format = SND_PCM_FORMAT_U24_3LE; break;
58     case 32: format = SND_PCM_FORMAT_U32_LE;  break;
59    }
60   break;
61  case ROAR_CODEC_PCM_U_BE:
62    switch (info->bits) {
63     case  8: format = SND_PCM_FORMAT_U8;      break;
64     case 16: format = SND_PCM_FORMAT_U16_BE;  break;
65     case 24: format = SND_PCM_FORMAT_U24_3BE; break;
66     case 32: format = SND_PCM_FORMAT_U32_BE;  break;
67    }
68   break;
69  case ROAR_CODEC_ALAW:
70    format = SND_PCM_FORMAT_A_LAW;
71   break;
72  case ROAR_CODEC_MULAW:
73    format = SND_PCM_FORMAT_MU_LAW;
74   break;
75  case ROAR_CODEC_GSM:
76    format = SND_PCM_FORMAT_GSM;
77   break;
78 }
79
80 return format;
81}
82
83static int driver_alsa_set_params(roar_alsa_t * device) {
84 unsigned int      buffer_time = 32000; // 32ms buffer if possible.
85                                        // *_buffer_time_near() will pick something as close as possible.
86 snd_pcm_uframes_t frames      = ROAR_OUTPUT_BUFFER_SAMPLES;
87 int               rc;
88
89 ROAR_DBG("driver_alsa_set_params(device=%p) = ?", device);
90
91 if ( snd_pcm_hw_params_malloc(&device->params) < 0 )
92  return -1;
93
94 ROAR_DBG("driver_alsa_set_params(device=%p) = ?", device);
95
96 if ( snd_pcm_hw_params_any(device->handle, device->params) < 0 )
97  goto error;
98
99 ROAR_DBG("driver_alsa_set_params(device=%p) = ?", device);
100
101 if ( snd_pcm_hw_params_set_access(device->handle, device->params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0 )
102  goto error;
103
104 ROAR_DBG("driver_alsa_set_params(device=%p) = ?", device);
105
106 if ( snd_pcm_hw_params_set_format(device->handle, device->params, device->format) < 0)
107  goto error;
108
109 ROAR_DBG("driver_alsa_set_params(device=%p) = ?", device);
110
111 ROAR_DBG("driver_alsa_set_params(device=%p): device->info.channels=%i", device, device->info.channels);
112
113 if ( snd_pcm_hw_params_set_channels(device->handle, device->params, device->info.channels) < 0 )
114  goto error;
115
116 ROAR_DBG("driver_alsa_set_params(device=%p) = ?", device);
117
118 if ( snd_pcm_hw_params_set_rate(device->handle, device->params, device->info.rate, 0) < 0 )
119  goto error;
120
121 ROAR_DBG("driver_alsa_set_params(device=%p) = ?", device);
122
123 if ( snd_pcm_hw_params_set_buffer_time_near(device->handle, device->params, &buffer_time, NULL) < 0 )
124  goto error;
125
126 ROAR_DBG("driver_alsa_set_params(device=%p) = ?", device);
127
128 if ( snd_pcm_hw_params_set_period_size_near(device->handle, device->params, &frames, NULL) < 0 )
129  goto error;
130
131 ROAR_DBG("driver_alsa_set_params(device=%p) = ?", device);
132
133 rc = snd_pcm_hw_params(device->handle, device->params);
134 if (rc < 0) {
135  ROAR_ERR("driver_alsa_open_vio(*): unable to set hw parameters: %s", snd_strerror(rc));
136  goto error;
137 }
138
139 ROAR_DBG("driver_alsa_set_params(device=%p) = ?", device);
140
141 snd_pcm_hw_params_free(device->params);
142
143 ROAR_DBG("driver_alsa_set_params(device=%p) = 0", device);
144
145 return 0;
146
147error:
148 snd_pcm_hw_params_free(device->params);
149 ROAR_DBG("driver_alsa_set_params(device=%p) = -1", device);
150 return -1;
151}
152
153int driver_alsa_open_vio(struct roar_vio_calls * inst, char * device, struct roar_audio_info * info, int fh, struct roar_stream_server * sstream) {
154 roar_alsa_t     * interface;
155 char            * alsa_dev;
156 int               rc;
157 int               autoconf;
158
159 ROAR_DBG("driver_alsa_open_vio(inst=%p, device='%s', info=%p, fh=%i, sstream=%p) = ?", inst, device, info, fh, sstream);
160
161 if ( fh != -1 )
162  return -1;
163
164 if ( (interface = roar_mm_calloc(1, sizeof(roar_alsa_t))) == NULL )
165  return -1;
166
167 ROAR_DBG("driver_alsa_open_vio(inst=%p, device='%s', info=%p, fh=%i, sstream=%p) = ?", inst, device, info, fh, sstream);
168
169 if ( device == NULL ) {
170  alsa_dev = "default";
171 } else {
172  alsa_dev = device;
173 }
174
175 ROAR_DBG("driver_alsa_open_vio(inst=%p, device='%s', info=%p, fh=%i, sstream=%p) = ?", inst, device, info, fh, sstream);
176
177 rc = snd_pcm_open(&(interface->handle), alsa_dev, SND_PCM_STREAM_PLAYBACK, 0);
178 if ( rc < 0 ) {
179  ROAR_ERR("driver_alsa_open_vio(*): Unable to open PCM device: %s", snd_strerror(rc));
180  roar_mm_free(interface);
181  return -1;
182 }
183
184 // Setting sample format, yay.
185 interface->format = driver_alsa_roar_fmt_to_alsa(info);
186
187 ROAR_DBG("driver_alsa_open_vio(inst=%p, device='%s', info=%p, fh=%i, sstream=%p) = ?", inst, device, info, fh, sstream);
188
189 // We did not find a codec
190 if ( interface->format == SND_PCM_FORMAT_UNKNOWN ) {
191  autoconf = streams_get_flag(ROAR_STREAM(sstream)->id, ROAR_FLAG_AUTOCONF);
192  if ( autoconf == -1 ) {
193   ROAR_ERR("driver_alsa_open_vio(*): Could not get autoconf flag.");
194   goto init_error;
195  }
196
197  if ( autoconf ) {
198   info->bits  = 16;
199
200#if   ROAR_CODEC_DEFAULT == ROAR_CODEC_PCM_S_LE
201   interface->format = SND_PCM_FORMAT_S16_LE;
202   info->codec       = ROAR_CODEC_PCM_S_LE;
203#elif ROAR_CODEC_DEFAULT == ROAR_CODEC_PCM_S_BE
204   interface->format = SND_PCM_FORMAT_S16_BE;
205   info->codec       = ROAR_CODEC_PCM_S_BE;
206#else
207   ROAR_ERR("driver_alsa_open_vio(*): ALSA only support little- or big-endian sample format. Please select -oO codec=pcm_s_le or pcm_s_be");
208   goto init_error;
209#endif
210   ROAR_WARN("driver_alsa_open_vio(*): Supplied codec type not available. Using 16 bit sample format.");
211  } else {
212   ROAR_ERR("driver_alsa_open_vio(*): can not set requested codec, set codec manually with -oO codec=<codec>.");
213   goto init_error;
214  }
215 }
216
217 memcpy(&(interface->info), info, sizeof(struct roar_audio_info));
218
219 ROAR_DBG("driver_alsa_open_vio(inst=%p, device='%s', info=%p, fh=%i, sstream=%p) = ?", inst, device, info, fh, sstream);
220
221 if ( driver_alsa_set_params(interface) < 0 )
222  goto init_error;
223
224 ROAR_DBG("driver_alsa_open_vio(inst=%p, device='%s', info=%p, fh=%i, sstream=%p) = ?", inst, device, info, fh, sstream);
225
226 memset(inst, 0, sizeof(struct roar_vio_calls));
227 inst->flags    = ROAR_VIO_FLAGS_NONE;
228 inst->refc     = 1;
229
230 inst->inst     = (void*) interface;
231 inst->write    = driver_alsa_write;
232 inst->close    = driver_alsa_close;
233 inst->sync     = driver_alsa_sync;
234 inst->ctl      = driver_alsa_ctl;
235
236 ROAR_DBG("driver_alsa_open_vio(inst=%p, device='%s', info=%p, fh=%i, sstream=%p) = 0", inst, device, info, fh, sstream);
237
238 return 0;
239
240init_error:
241 snd_pcm_close(interface->handle);
242 roar_mm_free(interface);
243 return -1;
244}
245
246int driver_alsa_close(struct roar_vio_calls * vio) {
247 roar_alsa_t * device = vio->inst;
248
249 if ( device->handle != NULL ) {
250  snd_pcm_drain(device->handle);
251  snd_pcm_close(device->handle);
252 }
253
254 roar_mm_free(device);
255
256 return 0;
257}
258
259static ssize_t driver_alsa_write_int(struct roar_vio_calls * vio, void *buf, size_t size) {
260 roar_alsa_t       * device    = vio->inst;
261 snd_pcm_sframes_t  write_size = snd_pcm_bytes_to_frames(device->handle, size);
262 snd_pcm_sframes_t  rc;
263
264 ROAR_DBG("driver_alsa_write(vio=%p, buf=%p, size=%llu) = ?", vio, buf, (long long unsigned int)size);
265
266 rc = snd_pcm_writei(device->handle, buf, write_size);
267
268 if ( rc == -EPIPE || rc == -EINTR || rc == -ESTRPIPE ) {
269  if ( snd_pcm_recover(device->handle, rc, 1) < 0 )
270   return -1;
271  return size;
272 } else if (rc < 0) {
273  ROAR_ERR("driver_alsa_write(*): Error from snd_pcm_writei(*): %s", snd_strerror(rc));
274  return -1;
275 } 
276
277 return snd_pcm_frames_to_bytes(device->handle, rc);
278}
279
280ssize_t driver_alsa_write(struct roar_vio_calls * vio, void *buf, size_t size) {
281 char*   mutable_buf = (char*)buf;
282 ssize_t have        = 0;
283 ssize_t ret;
284
285 while (size) {
286  ret = driver_alsa_write_int(vio, mutable_buf, size);
287  if ( ret < 0 )
288   return -1;
289
290  have        += ret;
291  mutable_buf += ret;
292  size        -= ret;
293 }
294
295 return have;
296}
297
298int driver_alsa_sync(struct roar_vio_calls * vio) {
299 roar_alsa_t       * device    = vio->inst;
300 return snd_pcm_drain(device->handle);
301}
302
303/* Stop the driver, set new params, and restart */
304/* Not been able to test this function. */
305/* Assuming that params can be reset after halting the PCM device */
306static int driver_alsa_reopen_device(roar_alsa_t * device) {
307
308 if ( snd_pcm_drop(device->handle) < 0 ) {
309  ROAR_ERR("driver_alsa_reopen_device(*): snd_pcm_drop() failed.");
310  return -1;
311 }
312
313 if ( driver_alsa_set_params(device) < 0 ) {
314  ROAR_ERR("driver_alsa_reopen_device(*): driver_alsa_set_params() failed.");
315  return -1;
316 }
317
318 return 0;
319}
320
321/* Not completely tested.
322 * Tested:
323 * ROAR_VIO_CTL_GET_DELAY
324 * ROAR_VIO_CTL_SET_SSTREAMID
325 * ROAR_VIO_CTL_SET_SSTREAM
326 */
327
328int driver_alsa_ctl(struct roar_vio_calls * vio, roar_vio_ctl_t cmd, void * data) {
329 roar_alsa_t       * device    = vio->inst;
330 snd_pcm_sframes_t alsa_delay;
331
332 ROAR_DBG("driver_alsa_ctl(vio=%p, cmd=0x%.8x, data=%p) = ?", vio, cmd, data);
333
334 switch (cmd) {
335  case ROAR_VIO_CTL_GET_DELAY:
336    if ( snd_pcm_delay(device->handle, &alsa_delay) < 0 )
337     return -1;
338    else {
339     *(uint_least32_t *)data = snd_pcm_frames_to_bytes(device->handle, alsa_delay);
340     ROAR_DBG("driver_alsa_ctl(vio=%p, cmd=ROAR_VIO_CTL_GET_DELAY(0x%.8x), data=%p): %i bytes.", vio, cmd, data, (int)(*(uint_least32_t*)data));
341    }
342   break;
343
344  case ROAR_VIO_CTL_SET_SSTREAMID:
345    device->ssid = *(int *)data;
346    ROAR_DBG("driver_alsa_ctl(vio=%p, cmd=ROAR_VIO_CTL_SET_SSTREAMID(0x%.8x), data=%p): ssid=%i", vio, cmd, data, device->ssid);
347   break;
348
349  case ROAR_VIO_CTL_SET_SSTREAM:
350    device->sstream = data;
351    ROAR_DBG("driver_alsa_ctl(vio=%p, cmd=ROAR_VIO_CTL_SET_SSTREAM(0x%.8x), data=%p): sstream=%p", vio, cmd, data, device->sstream);
352   break;
353
354  case ROAR_VIO_CTL_GET_AUINFO:
355    ROAR_DBG("driver_alsa_ctl(vio=%p, cmd=ROAR_VIO_CTL_GET_AUINFO(0x%.8x), data=%p) = ?", vio, cmd, data);
356    memcpy(data, &(device->info), sizeof(struct roar_audio_info));
357   break;
358
359  case ROAR_VIO_CTL_SET_AUINFO:
360    ROAR_DBG("driver_alsa_ctl(vio=%p, cmd=ROAR_VIO_CTL_SET_AUINFO(0x%.8x), data=%p) = ?", vio, cmd, data);
361    memcpy(&(device->info), data, sizeof(struct roar_audio_info));
362    return driver_alsa_reopen_device(device);
363   break;
364
365  case ROAR_VIO_CTL_NONBLOCK:
366    if ( *(int*)data == ROAR_SOCKET_BLOCK )
367     return 0;
368    roar_err_set(ROAR_ERROR_NOTSUP);
369    return -1;
370   break;
371
372  default:
373   return -1;
374 }
375
376 return 0;
377}
378
379#endif
380
Note: See TracBrowser for help on using the repository browser.