diff options
Diffstat (limited to 'firmware/target/hosted/pcm-alsa.c')
-rw-r--r-- | firmware/target/hosted/pcm-alsa.c | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/firmware/target/hosted/pcm-alsa.c b/firmware/target/hosted/pcm-alsa.c new file mode 100644 index 0000000000..928187993e --- /dev/null +++ b/firmware/target/hosted/pcm-alsa.c | |||
@@ -0,0 +1,518 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2010 Thomas Martitz | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | |||
23 | /* | ||
24 | * Based, but heavily modified, on the example given at | ||
25 | * http://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html | ||
26 | * | ||
27 | * This driver uses the so-called unsafe async callback method and hardcoded device | ||
28 | * names. It fails when the audio device is busy by other apps. | ||
29 | * | ||
30 | * TODO: Rewrite this to do it properly with multithreading | ||
31 | * | ||
32 | * Alternatively, a version using polling in a tick task is provided. While | ||
33 | * supposedly safer, it appears to use more CPU (however I didn't measure it | ||
34 | * accurately, only looked at htop). At least, in this mode the "default" | ||
35 | * device works which doesnt break with other apps running. | ||
36 | * device works which doesnt break with other apps running. | ||
37 | */ | ||
38 | |||
39 | |||
40 | #include "autoconf.h" | ||
41 | |||
42 | #include <stdlib.h> | ||
43 | #include <stdbool.h> | ||
44 | #include <alsa/asoundlib.h> | ||
45 | #include "system.h" | ||
46 | #include "debug.h" | ||
47 | #include "kernel.h" | ||
48 | |||
49 | #include "pcm.h" | ||
50 | #include "pcm-internal.h" | ||
51 | #include "pcm_mixer.h" | ||
52 | #include "pcm_sampr.h" | ||
53 | |||
54 | #include <pthread.h> | ||
55 | #include <signal.h> | ||
56 | |||
57 | #define USE_ASYNC_CALLBACK | ||
58 | /* plughw:0,0 works with both, however "default" is recommended. | ||
59 | * default doesnt seem to work with async callback but doesn't break | ||
60 | * with multple applications running */ | ||
61 | static char device[] = "plughw:0,0"; /* playback device */ | ||
62 | static const snd_pcm_access_t access_ = SND_PCM_ACCESS_RW_INTERLEAVED; /* access mode */ | ||
63 | static const snd_pcm_format_t format = SND_PCM_FORMAT_S16; /* sample format */ | ||
64 | static const int channels = 2; /* count of channels */ | ||
65 | static unsigned int rate = 44100; /* stream rate */ | ||
66 | |||
67 | static snd_pcm_t *handle; | ||
68 | static snd_pcm_sframes_t buffer_size = MIX_FRAME_SAMPLES * 32; /* ~16k */ | ||
69 | static snd_pcm_sframes_t period_size = MIX_FRAME_SAMPLES * 4; /* ~4k */ | ||
70 | static short *frames; | ||
71 | |||
72 | static const char *pcm_data = 0; | ||
73 | static size_t pcm_size = 0; | ||
74 | |||
75 | #ifdef USE_ASYNC_CALLBACK | ||
76 | static snd_async_handler_t *ahandler; | ||
77 | static pthread_mutex_t pcm_mtx; | ||
78 | #else | ||
79 | static int recursion; | ||
80 | #endif | ||
81 | |||
82 | static int set_hwparams(snd_pcm_t *handle, unsigned sample_rate) | ||
83 | { | ||
84 | unsigned int rrate; | ||
85 | int err; | ||
86 | snd_pcm_hw_params_t *params; | ||
87 | snd_pcm_hw_params_alloca(¶ms); | ||
88 | |||
89 | |||
90 | /* choose all parameters */ | ||
91 | err = snd_pcm_hw_params_any(handle, params); | ||
92 | if (err < 0) | ||
93 | { | ||
94 | printf("Broken configuration for playback: no configurations available: %s\n", snd_strerror(err)); | ||
95 | return err; | ||
96 | } | ||
97 | /* set the interleaved read/write format */ | ||
98 | err = snd_pcm_hw_params_set_access(handle, params, access_); | ||
99 | if (err < 0) | ||
100 | { | ||
101 | printf("Access type not available for playback: %s\n", snd_strerror(err)); | ||
102 | return err; | ||
103 | } | ||
104 | /* set the sample format */ | ||
105 | err = snd_pcm_hw_params_set_format(handle, params, format); | ||
106 | if (err < 0) | ||
107 | { | ||
108 | printf("Sample format not available for playback: %s\n", snd_strerror(err)); | ||
109 | return err; | ||
110 | } | ||
111 | /* set the count of channels */ | ||
112 | err = snd_pcm_hw_params_set_channels(handle, params, channels); | ||
113 | if (err < 0) | ||
114 | { | ||
115 | printf("Channels count (%i) not available for playbacks: %s\n", channels, snd_strerror(err)); | ||
116 | return err; | ||
117 | } | ||
118 | /* set the stream rate */ | ||
119 | rrate = sample_rate; | ||
120 | err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0); | ||
121 | if (err < 0) | ||
122 | { | ||
123 | printf("Rate %iHz not available for playback: %s\n", rate, snd_strerror(err)); | ||
124 | return err; | ||
125 | } | ||
126 | if (rrate != sample_rate) | ||
127 | { | ||
128 | printf("Rate doesn't match (requested %iHz, get %iHz)\n", sample_rate, err); | ||
129 | return -EINVAL; | ||
130 | } | ||
131 | |||
132 | /* set the buffer size */ | ||
133 | err = snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_size); | ||
134 | if (err < 0) | ||
135 | { | ||
136 | printf("Unable to set buffer size %i for playback: %s\n", buffer_size, snd_strerror(err)); | ||
137 | return err; | ||
138 | } | ||
139 | |||
140 | /* set the period size */ | ||
141 | err = snd_pcm_hw_params_set_period_size_near (handle, params, &period_size, NULL); | ||
142 | if (err < 0) | ||
143 | { | ||
144 | printf("Unable to set period size %i for playback: %s\n", period_size, snd_strerror(err)); | ||
145 | return err; | ||
146 | } | ||
147 | if (!frames) | ||
148 | frames = malloc(period_size * channels * sizeof(short)); | ||
149 | |||
150 | /* write the parameters to device */ | ||
151 | err = snd_pcm_hw_params(handle, params); | ||
152 | if (err < 0) | ||
153 | { | ||
154 | printf("Unable to set hw params for playback: %s\n", snd_strerror(err)); | ||
155 | return err; | ||
156 | } | ||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | /* Set sw params: playback start threshold and low buffer watermark */ | ||
161 | static int set_swparams(snd_pcm_t *handle) | ||
162 | { | ||
163 | int err; | ||
164 | |||
165 | snd_pcm_sw_params_t *swparams; | ||
166 | snd_pcm_sw_params_alloca(&swparams); | ||
167 | |||
168 | /* get the current swparams */ | ||
169 | err = snd_pcm_sw_params_current(handle, swparams); | ||
170 | if (err < 0) | ||
171 | { | ||
172 | printf("Unable to determine current swparams for playback: %s\n", snd_strerror(err)); | ||
173 | return err; | ||
174 | } | ||
175 | /* start the transfer when the buffer is haalmost full */ | ||
176 | err = snd_pcm_sw_params_set_start_threshold(handle, swparams, buffer_size / 2); | ||
177 | if (err < 0) | ||
178 | { | ||
179 | printf("Unable to set start threshold mode for playback: %s\n", snd_strerror(err)); | ||
180 | return err; | ||
181 | } | ||
182 | /* allow the transfer when at least period_size samples can be processed */ | ||
183 | err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_size); | ||
184 | if (err < 0) | ||
185 | { | ||
186 | printf("Unable to set avail min for playback: %s\n", snd_strerror(err)); | ||
187 | return err; | ||
188 | } | ||
189 | /* write the parameters to the playback device */ | ||
190 | err = snd_pcm_sw_params(handle, swparams); | ||
191 | if (err < 0) | ||
192 | { | ||
193 | printf("Unable to set sw params for playback: %s\n", snd_strerror(err)); | ||
194 | return err; | ||
195 | } | ||
196 | return 0; | ||
197 | } | ||
198 | |||
199 | /* copy pcm samples to a spare buffer, suitable for snd_pcm_writei() */ | ||
200 | static bool fill_frames(void) | ||
201 | { | ||
202 | ssize_t copy_n, frames_left = period_size; | ||
203 | bool new_buffer = false; | ||
204 | |||
205 | while (frames_left > 0) | ||
206 | { | ||
207 | if (!pcm_size) | ||
208 | { | ||
209 | new_buffer = true; | ||
210 | pcm_play_get_more_callback((void **)&pcm_data, &pcm_size); | ||
211 | if (!pcm_size || !pcm_data) | ||
212 | return false; | ||
213 | } | ||
214 | copy_n = MIN((ssize_t)pcm_size, frames_left*4); | ||
215 | memcpy(&frames[2*(period_size-frames_left)], pcm_data, copy_n); | ||
216 | |||
217 | pcm_data += copy_n; | ||
218 | pcm_size -= copy_n; | ||
219 | frames_left -= copy_n/4; | ||
220 | |||
221 | if (new_buffer) | ||
222 | { | ||
223 | new_buffer = false; | ||
224 | pcm_play_dma_started_callback(); | ||
225 | } | ||
226 | } | ||
227 | return true; | ||
228 | } | ||
229 | |||
230 | #ifdef USE_ASYNC_CALLBACK | ||
231 | static void async_callback(snd_async_handler_t *ahandler) | ||
232 | { | ||
233 | snd_pcm_t *handle = snd_async_handler_get_pcm(ahandler); | ||
234 | |||
235 | if (pthread_mutex_trylock(&pcm_mtx) != 0) | ||
236 | return; | ||
237 | #else | ||
238 | static void pcm_tick(void) | ||
239 | { | ||
240 | if (snd_pcm_state(handle) != SND_PCM_STATE_RUNNING) | ||
241 | return; | ||
242 | #endif | ||
243 | |||
244 | while (snd_pcm_avail_update(handle) >= period_size) | ||
245 | { | ||
246 | if (fill_frames()) | ||
247 | { | ||
248 | int err = snd_pcm_writei(handle, frames, period_size); | ||
249 | if (err < 0 && err != period_size && err != -EAGAIN) | ||
250 | { | ||
251 | printf("Write error: written %i expected %li\n", err, period_size); | ||
252 | break; | ||
253 | } | ||
254 | } | ||
255 | else | ||
256 | { | ||
257 | DEBUGF("%s: No Data.\n", __func__); | ||
258 | break; | ||
259 | } | ||
260 | } | ||
261 | #ifdef USE_ASYNC_CALLBACK | ||
262 | pthread_mutex_unlock(&pcm_mtx); | ||
263 | #endif | ||
264 | } | ||
265 | |||
266 | static int async_rw(snd_pcm_t *handle) | ||
267 | { | ||
268 | int err; | ||
269 | snd_pcm_sframes_t sample_size; | ||
270 | short *samples; | ||
271 | |||
272 | #ifdef USE_ASYNC_CALLBACK | ||
273 | err = snd_async_add_pcm_handler(&ahandler, handle, async_callback, NULL); | ||
274 | if (err < 0) | ||
275 | { | ||
276 | DEBUGF("Unable to register async handler: %s\n", snd_strerror(err)); | ||
277 | return err; | ||
278 | } | ||
279 | #endif | ||
280 | |||
281 | /* fill buffer with silence to initiate playback without noisy click */ | ||
282 | sample_size = buffer_size; | ||
283 | samples = malloc(sample_size * channels * sizeof(short)); | ||
284 | |||
285 | snd_pcm_format_set_silence(format, samples, sample_size); | ||
286 | err = snd_pcm_writei(handle, samples, sample_size); | ||
287 | free(samples); | ||
288 | |||
289 | if (err < 0) | ||
290 | { | ||
291 | DEBUGF("Initial write error: %s\n", snd_strerror(err)); | ||
292 | return err; | ||
293 | } | ||
294 | if (err != (ssize_t)sample_size) | ||
295 | { | ||
296 | DEBUGF("Initial write error: written %i expected %li\n", err, sample_size); | ||
297 | return err; | ||
298 | } | ||
299 | if (snd_pcm_state(handle) == SND_PCM_STATE_PREPARED) | ||
300 | { | ||
301 | err = snd_pcm_start(handle); | ||
302 | if (err < 0) | ||
303 | { | ||
304 | DEBUGF("Start error: %s\n", snd_strerror(err)); | ||
305 | return err; | ||
306 | } | ||
307 | } | ||
308 | return 0; | ||
309 | } | ||
310 | |||
311 | |||
312 | void cleanup(void) | ||
313 | { | ||
314 | free(frames); | ||
315 | frames = NULL; | ||
316 | snd_pcm_close(handle); | ||
317 | } | ||
318 | |||
319 | |||
320 | void pcm_play_dma_init(void) | ||
321 | { | ||
322 | int err; | ||
323 | |||
324 | |||
325 | if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) | ||
326 | { | ||
327 | printf("%s(): Cannot open device %s: %s\n", __func__, device, snd_strerror(err)); | ||
328 | exit(EXIT_FAILURE); | ||
329 | return; | ||
330 | } | ||
331 | |||
332 | if ((err = snd_pcm_nonblock(handle, 1))) | ||
333 | printf("Could not set non-block mode: %s\n", snd_strerror(err)); | ||
334 | |||
335 | if ((err = set_hwparams(handle, rate)) < 0) | ||
336 | { | ||
337 | printf("Setting of hwparams failed: %s\n", snd_strerror(err)); | ||
338 | exit(EXIT_FAILURE); | ||
339 | } | ||
340 | if ((err = set_swparams(handle)) < 0) | ||
341 | { | ||
342 | printf("Setting of swparams failed: %s\n", snd_strerror(err)); | ||
343 | exit(EXIT_FAILURE); | ||
344 | } | ||
345 | |||
346 | #ifdef USE_ASYNC_CALLBACK | ||
347 | pthread_mutexattr_t attr; | ||
348 | pthread_mutexattr_init(&attr); | ||
349 | pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); | ||
350 | pthread_mutex_init(&pcm_mtx, &attr); | ||
351 | #else | ||
352 | tick_add_task(pcm_tick); | ||
353 | #endif | ||
354 | |||
355 | |||
356 | atexit(cleanup); | ||
357 | return; | ||
358 | } | ||
359 | |||
360 | |||
361 | void pcm_play_lock(void) | ||
362 | { | ||
363 | #ifdef USE_ASYNC_CALLBACK | ||
364 | pthread_mutex_lock(&pcm_mtx); | ||
365 | #else | ||
366 | if (recursion++ == 0) | ||
367 | tick_remove_task(pcm_tick); | ||
368 | #endif | ||
369 | } | ||
370 | |||
371 | void pcm_play_unlock(void) | ||
372 | { | ||
373 | #ifdef USE_ASYNC_CALLBACK | ||
374 | pthread_mutex_unlock(&pcm_mtx); | ||
375 | #else | ||
376 | if (--recursion == 0) | ||
377 | tick_add_task(pcm_tick); | ||
378 | #endif | ||
379 | } | ||
380 | |||
381 | static void pcm_dma_apply_settings_nolock(void) | ||
382 | { | ||
383 | snd_pcm_drop(handle); | ||
384 | set_hwparams(handle, pcm_sampr); | ||
385 | } | ||
386 | |||
387 | void pcm_dma_apply_settings(void) | ||
388 | { | ||
389 | pcm_play_lock(); | ||
390 | pcm_dma_apply_settings_nolock(); | ||
391 | pcm_play_unlock(); | ||
392 | } | ||
393 | |||
394 | |||
395 | void pcm_play_dma_pause(bool pause) | ||
396 | { | ||
397 | snd_pcm_pause(handle, pause); | ||
398 | } | ||
399 | |||
400 | |||
401 | void pcm_play_dma_stop(void) | ||
402 | { | ||
403 | snd_pcm_drain(handle); | ||
404 | } | ||
405 | |||
406 | void pcm_play_dma_start(const void *addr, size_t size) | ||
407 | { | ||
408 | pcm_dma_apply_settings_nolock(); | ||
409 | |||
410 | pcm_data = addr; | ||
411 | pcm_size = size; | ||
412 | |||
413 | while (1) | ||
414 | { | ||
415 | snd_pcm_state_t state = snd_pcm_state(handle); | ||
416 | switch (state) | ||
417 | { | ||
418 | case SND_PCM_STATE_RUNNING: | ||
419 | return; | ||
420 | case SND_PCM_STATE_XRUN: | ||
421 | { | ||
422 | DEBUGF("Trying to recover from error\n"); | ||
423 | int err = snd_pcm_recover(handle, -EPIPE, 0); | ||
424 | if (err < 0) | ||
425 | DEBUGF("Recovery failed: %s\n", snd_strerror(err)); | ||
426 | continue; | ||
427 | } | ||
428 | case SND_PCM_STATE_SETUP: | ||
429 | { | ||
430 | int err = snd_pcm_prepare(handle); | ||
431 | if (err < 0) | ||
432 | printf("Prepare error: %s\n", snd_strerror(err)); | ||
433 | /* fall through */ | ||
434 | } | ||
435 | case SND_PCM_STATE_PREPARED: | ||
436 | { /* prepared state, we need to fill the buffer with silence before | ||
437 | * starting */ | ||
438 | int err = async_rw(handle); | ||
439 | if (err < 0) | ||
440 | printf("Start error: %s\n", snd_strerror(err)); | ||
441 | return; | ||
442 | } | ||
443 | case SND_PCM_STATE_PAUSED: | ||
444 | { /* paused, simply resume */ | ||
445 | pcm_play_dma_pause(0); | ||
446 | return; | ||
447 | } | ||
448 | case SND_PCM_STATE_DRAINING: | ||
449 | /* run until drained */ | ||
450 | continue; | ||
451 | default: | ||
452 | DEBUGF("Unhandled state: %s\n", snd_pcm_state_name(state)); | ||
453 | return; | ||
454 | } | ||
455 | } | ||
456 | } | ||
457 | |||
458 | size_t pcm_get_bytes_waiting(void) | ||
459 | { | ||
460 | return pcm_size; | ||
461 | } | ||
462 | |||
463 | const void * pcm_play_dma_get_peak_buffer(int *count) | ||
464 | { | ||
465 | uintptr_t addr = (uintptr_t)pcm_data; | ||
466 | *count = pcm_size / 4; | ||
467 | return (void *)((addr + 3) & ~3); | ||
468 | } | ||
469 | |||
470 | void pcm_play_dma_postinit(void) | ||
471 | { | ||
472 | } | ||
473 | |||
474 | |||
475 | void pcm_set_mixer_volume(int volume) | ||
476 | { | ||
477 | (void)volume; | ||
478 | } | ||
479 | #ifdef HAVE_RECORDING | ||
480 | void pcm_rec_lock(void) | ||
481 | { | ||
482 | } | ||
483 | |||
484 | void pcm_rec_unlock(void) | ||
485 | { | ||
486 | } | ||
487 | |||
488 | void pcm_rec_dma_init(void) | ||
489 | { | ||
490 | } | ||
491 | |||
492 | void pcm_rec_dma_close(void) | ||
493 | { | ||
494 | } | ||
495 | |||
496 | void pcm_rec_dma_start(void *start, size_t size) | ||
497 | { | ||
498 | (void)start; | ||
499 | (void)size; | ||
500 | } | ||
501 | |||
502 | void pcm_rec_dma_stop(void) | ||
503 | { | ||
504 | } | ||
505 | |||
506 | const void * pcm_rec_dma_get_peak_buffer(void) | ||
507 | { | ||
508 | return NULL; | ||
509 | } | ||
510 | |||
511 | void audiohw_set_recvol(int left, int right, int type) | ||
512 | { | ||
513 | (void)left; | ||
514 | (void)right; | ||
515 | (void)type; | ||
516 | } | ||
517 | |||
518 | #endif /* HAVE_RECORDING */ | ||