[3693] | 1 | //driver_alsa.c: |
---|
| 2 | |
---|
| 3 | /* |
---|
[4708] | 4 | * Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2008-2011 |
---|
[3693] | 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 | |
---|
[4160] | 33 | static 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 | |
---|
| 83 | static 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 | |
---|
| 147 | error: |
---|
| 148 | snd_pcm_hw_params_free(device->params); |
---|
| 149 | ROAR_DBG("driver_alsa_set_params(device=%p) = -1", device); |
---|
| 150 | return -1; |
---|
| 151 | } |
---|
[3693] | 152 | |
---|
| 153 | int 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 | |
---|
[4160] | 159 | ROAR_DBG("driver_alsa_open_vio(inst=%p, device='%s', info=%p, fh=%i, sstream=%p) = ?", inst, device, info, fh, sstream); |
---|
[3693] | 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 | |
---|
[4160] | 167 | ROAR_DBG("driver_alsa_open_vio(inst=%p, device='%s', info=%p, fh=%i, sstream=%p) = ?", inst, device, info, fh, sstream); |
---|
| 168 | |
---|
[3693] | 169 | if ( device == NULL ) { |
---|
| 170 | alsa_dev = "default"; |
---|
| 171 | } else { |
---|
| 172 | alsa_dev = device; |
---|
| 173 | } |
---|
| 174 | |
---|
[4160] | 175 | ROAR_DBG("driver_alsa_open_vio(inst=%p, device='%s', info=%p, fh=%i, sstream=%p) = ?", inst, device, info, fh, sstream); |
---|
| 176 | |
---|
[3693] | 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. |
---|
[4160] | 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); |
---|
[3693] | 188 | |
---|
| 189 | // We did not find a codec |
---|
[4160] | 190 | if ( interface->format == SND_PCM_FORMAT_UNKNOWN ) { |
---|
[3693] | 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 |
---|
[4160] | 201 | interface->format = SND_PCM_FORMAT_S16_LE; |
---|
| 202 | info->codec = ROAR_CODEC_PCM_S_LE; |
---|
[3693] | 203 | #elif ROAR_CODEC_DEFAULT == ROAR_CODEC_PCM_S_BE |
---|
[4160] | 204 | interface->format = SND_PCM_FORMAT_S16_BE; |
---|
| 205 | info->codec = ROAR_CODEC_PCM_S_BE; |
---|
[3693] | 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 | |
---|
[4160] | 217 | memcpy(&(interface->info), info, sizeof(struct roar_audio_info)); |
---|
[3693] | 218 | |
---|
[4160] | 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 ) |
---|
[3693] | 222 | goto init_error; |
---|
| 223 | |
---|
[4160] | 224 | ROAR_DBG("driver_alsa_open_vio(inst=%p, device='%s', info=%p, fh=%i, sstream=%p) = ?", inst, device, info, fh, sstream); |
---|
[3693] | 225 | |
---|
| 226 | memset(inst, 0, sizeof(struct roar_vio_calls)); |
---|
| 227 | |
---|
| 228 | inst->inst = (void*) interface; |
---|
| 229 | inst->write = driver_alsa_write; |
---|
| 230 | inst->close = driver_alsa_close; |
---|
[4160] | 231 | inst->sync = driver_alsa_sync; |
---|
| 232 | inst->ctl = driver_alsa_ctl; |
---|
| 233 | |
---|
| 234 | ROAR_DBG("driver_alsa_open_vio(inst=%p, device='%s', info=%p, fh=%i, sstream=%p) = 0", inst, device, info, fh, sstream); |
---|
[3693] | 235 | |
---|
| 236 | return 0; |
---|
| 237 | |
---|
| 238 | init_error: |
---|
| 239 | snd_pcm_close(interface->handle); |
---|
| 240 | roar_mm_free(interface); |
---|
| 241 | return -1; |
---|
| 242 | } |
---|
| 243 | |
---|
| 244 | int driver_alsa_close(struct roar_vio_calls * vio) { |
---|
| 245 | roar_alsa_t * device = vio->inst; |
---|
| 246 | |
---|
| 247 | if ( device->handle != NULL ) { |
---|
| 248 | snd_pcm_drain(device->handle); |
---|
| 249 | snd_pcm_close(device->handle); |
---|
| 250 | } |
---|
| 251 | |
---|
| 252 | roar_mm_free(device); |
---|
| 253 | |
---|
| 254 | return 0; |
---|
| 255 | } |
---|
| 256 | |
---|
| 257 | static ssize_t driver_alsa_write_int(struct roar_vio_calls * vio, void *buf, size_t size) { |
---|
| 258 | roar_alsa_t * device = vio->inst; |
---|
| 259 | snd_pcm_sframes_t write_size = snd_pcm_bytes_to_frames(device->handle, size); |
---|
| 260 | snd_pcm_sframes_t rc; |
---|
| 261 | |
---|
| 262 | ROAR_DBG("driver_alsa_write(vio=%p, buf=%p, size=%llu) = ?", vio, buf, (long long unsigned int)size); |
---|
| 263 | |
---|
| 264 | rc = snd_pcm_writei(device->handle, buf, write_size); |
---|
| 265 | |
---|
| 266 | if ( rc == -EPIPE || rc == -EINTR || rc == -ESTRPIPE ) { |
---|
| 267 | if ( snd_pcm_recover(device->handle, rc, 1) < 0 ) |
---|
| 268 | return -1; |
---|
| 269 | return size; |
---|
| 270 | } else if (rc < 0) { |
---|
| 271 | ROAR_ERR("driver_alsa_write(*): Error from snd_pcm_writei(*): %s", snd_strerror(rc)); |
---|
| 272 | return -1; |
---|
| 273 | } |
---|
| 274 | |
---|
| 275 | return snd_pcm_frames_to_bytes(device->handle, rc); |
---|
| 276 | } |
---|
| 277 | |
---|
| 278 | ssize_t driver_alsa_write(struct roar_vio_calls * vio, void *buf, size_t size) { |
---|
[4160] | 279 | char* mutable_buf = (char*)buf; |
---|
| 280 | ssize_t have = 0; |
---|
[3693] | 281 | ssize_t ret; |
---|
| 282 | |
---|
| 283 | while (size) { |
---|
[4160] | 284 | ret = driver_alsa_write_int(vio, mutable_buf, size); |
---|
[3693] | 285 | if ( ret < 0 ) |
---|
| 286 | return -1; |
---|
| 287 | |
---|
[4160] | 288 | have += ret; |
---|
| 289 | mutable_buf += ret; |
---|
| 290 | size -= ret; |
---|
[3693] | 291 | } |
---|
| 292 | |
---|
| 293 | return have; |
---|
| 294 | } |
---|
| 295 | |
---|
[4160] | 296 | int driver_alsa_sync(struct roar_vio_calls * vio) { |
---|
| 297 | roar_alsa_t * device = vio->inst; |
---|
| 298 | return snd_pcm_drain(device->handle); |
---|
| 299 | } |
---|
| 300 | |
---|
| 301 | /* Stop the driver, set new params, and restart */ |
---|
| 302 | /* Not been able to test this function. */ |
---|
| 303 | /* Assuming that params can be reset after halting the PCM device */ |
---|
| 304 | static int driver_alsa_reopen_device(roar_alsa_t * device) { |
---|
| 305 | |
---|
| 306 | if ( snd_pcm_drop(device->handle) < 0 ) { |
---|
| 307 | ROAR_ERR("driver_alsa_reopen_device(*): snd_pcm_drop() failed."); |
---|
| 308 | return -1; |
---|
| 309 | } |
---|
| 310 | |
---|
| 311 | if ( driver_alsa_set_params(device) < 0 ) { |
---|
| 312 | ROAR_ERR("driver_alsa_reopen_device(*): driver_alsa_set_params() failed."); |
---|
| 313 | return -1; |
---|
| 314 | } |
---|
| 315 | |
---|
| 316 | return 0; |
---|
| 317 | } |
---|
| 318 | |
---|
| 319 | /* Not completely tested. |
---|
| 320 | * Tested: |
---|
| 321 | * ROAR_VIO_CTL_GET_DELAY |
---|
| 322 | * ROAR_VIO_CTL_SET_SSTREAMID |
---|
| 323 | * ROAR_VIO_CTL_SET_SSTREAM |
---|
| 324 | */ |
---|
| 325 | |
---|
| 326 | int driver_alsa_ctl(struct roar_vio_calls * vio, int cmd, void * data) { |
---|
| 327 | roar_alsa_t * device = vio->inst; |
---|
| 328 | snd_pcm_sframes_t alsa_delay; |
---|
| 329 | |
---|
| 330 | ROAR_DBG("driver_alsa_ctl(vio=%p, cmd=0x%.8x, data=%p) = ?", vio, cmd, data); |
---|
| 331 | |
---|
| 332 | switch (cmd) { |
---|
| 333 | case ROAR_VIO_CTL_GET_DELAY: |
---|
| 334 | if ( snd_pcm_delay(device->handle, &alsa_delay) < 0 ) |
---|
| 335 | return -1; |
---|
| 336 | else { |
---|
| 337 | *(uint_least32_t *)data = snd_pcm_frames_to_bytes(device->handle, alsa_delay); |
---|
| 338 | 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)); |
---|
| 339 | } |
---|
| 340 | break; |
---|
| 341 | |
---|
| 342 | case ROAR_VIO_CTL_SET_SSTREAMID: |
---|
| 343 | device->ssid = *(int *)data; |
---|
| 344 | ROAR_DBG("driver_alsa_ctl(vio=%p, cmd=ROAR_VIO_CTL_SET_SSTREAMID(0x%.8x), data=%p): ssid=%i", vio, cmd, data, device->ssid); |
---|
| 345 | break; |
---|
| 346 | |
---|
| 347 | case ROAR_VIO_CTL_SET_SSTREAM: |
---|
| 348 | device->sstream = data; |
---|
| 349 | ROAR_DBG("driver_alsa_ctl(vio=%p, cmd=ROAR_VIO_CTL_SET_SSTREAM(0x%.8x), data=%p): sstream=%p", vio, cmd, data, device->sstream); |
---|
| 350 | break; |
---|
| 351 | |
---|
| 352 | case ROAR_VIO_CTL_GET_AUINFO: |
---|
| 353 | ROAR_DBG("driver_alsa_ctl(vio=%p, cmd=ROAR_VIO_CTL_GET_AUINFO(0x%.8x), data=%p) = ?", vio, cmd, data); |
---|
| 354 | memcpy(data, &(device->info), sizeof(struct roar_audio_info)); |
---|
| 355 | break; |
---|
| 356 | |
---|
| 357 | case ROAR_VIO_CTL_SET_AUINFO: |
---|
| 358 | ROAR_DBG("driver_alsa_ctl(vio=%p, cmd=ROAR_VIO_CTL_SET_AUINFO(0x%.8x), data=%p) = ?", vio, cmd, data); |
---|
| 359 | memcpy(&(device->info), data, sizeof(struct roar_audio_info)); |
---|
| 360 | return driver_alsa_reopen_device(device); |
---|
| 361 | |
---|
| 362 | default: |
---|
| 363 | return -1; |
---|
| 364 | } |
---|
| 365 | |
---|
| 366 | return 0; |
---|
| 367 | } |
---|
| 368 | |
---|
[3693] | 369 | #endif |
---|
| 370 | |
---|