summaryrefslogtreecommitdiff
path: root/firmware/target/hosted/pcm-alsa.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/target/hosted/pcm-alsa.c')
-rw-r--r--firmware/target/hosted/pcm-alsa.c518
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 */
61static char device[] = "plughw:0,0"; /* playback device */
62static const snd_pcm_access_t access_ = SND_PCM_ACCESS_RW_INTERLEAVED; /* access mode */
63static const snd_pcm_format_t format = SND_PCM_FORMAT_S16; /* sample format */
64static const int channels = 2; /* count of channels */
65static unsigned int rate = 44100; /* stream rate */
66
67static snd_pcm_t *handle;
68static snd_pcm_sframes_t buffer_size = MIX_FRAME_SAMPLES * 32; /* ~16k */
69static snd_pcm_sframes_t period_size = MIX_FRAME_SAMPLES * 4; /* ~4k */
70static short *frames;
71
72static const char *pcm_data = 0;
73static size_t pcm_size = 0;
74
75#ifdef USE_ASYNC_CALLBACK
76static snd_async_handler_t *ahandler;
77static pthread_mutex_t pcm_mtx;
78#else
79static int recursion;
80#endif
81
82static 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(&params);
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 */
161static 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() */
200static 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
231static 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
238static 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
266static 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
312void cleanup(void)
313{
314 free(frames);
315 frames = NULL;
316 snd_pcm_close(handle);
317}
318
319
320void 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
361void 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
371void 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
381static void pcm_dma_apply_settings_nolock(void)
382{
383 snd_pcm_drop(handle);
384 set_hwparams(handle, pcm_sampr);
385}
386
387void pcm_dma_apply_settings(void)
388{
389 pcm_play_lock();
390 pcm_dma_apply_settings_nolock();
391 pcm_play_unlock();
392}
393
394
395void pcm_play_dma_pause(bool pause)
396{
397 snd_pcm_pause(handle, pause);
398}
399
400
401void pcm_play_dma_stop(void)
402{
403 snd_pcm_drain(handle);
404}
405
406void 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
458size_t pcm_get_bytes_waiting(void)
459{
460 return pcm_size;
461}
462
463const 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
470void pcm_play_dma_postinit(void)
471{
472}
473
474
475void pcm_set_mixer_volume(int volume)
476{
477 (void)volume;
478}
479#ifdef HAVE_RECORDING
480void pcm_rec_lock(void)
481{
482}
483
484void pcm_rec_unlock(void)
485{
486}
487
488void pcm_rec_dma_init(void)
489{
490}
491
492void pcm_rec_dma_close(void)
493{
494}
495
496void pcm_rec_dma_start(void *start, size_t size)
497{
498 (void)start;
499 (void)size;
500}
501
502void pcm_rec_dma_stop(void)
503{
504}
505
506const void * pcm_rec_dma_get_peak_buffer(void)
507{
508 return NULL;
509}
510
511void 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 */