Commit | Line | Data |
---|---|---|
4fd38868 RK |
1 | /* |
2 | * This file is part of DisOrder. | |
3 | * Copyright (C) 2009 Richard Kettlewell | |
4 | * | |
5 | * This program is free software: you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License as published by | |
7 | * the Free Software Foundation, either version 3 of the License, or | |
8 | * (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | */ | |
18 | /** @file lib/uaudio-alsa.c | |
19 | * @brief Support for ALSA backend */ | |
20 | #include "common.h" | |
21 | ||
22 | #if HAVE_ALSA_ASOUNDLIB_H | |
23 | ||
24 | #include <alsa/asoundlib.h> | |
25 | ||
26 | #include "mem.h" | |
27 | #include "log.h" | |
28 | #include "uaudio.h" | |
29 | ||
30 | /** @brief The current PCM handle */ | |
31 | static snd_pcm_t *alsa_pcm; | |
32 | ||
33 | static const char *const alsa_options[] = { | |
34 | "device", | |
35 | NULL | |
36 | }; | |
37 | ||
38 | /** @brief Actually play sound via ALSA */ | |
39 | static size_t alsa_play(void *buffer, size_t samples) { | |
40 | int err; | |
41 | /* ALSA wants 'frames', where frame = several concurrently played samples */ | |
42 | const snd_pcm_uframes_t frames = samples / uaudio_channels; | |
43 | ||
44 | snd_pcm_sframes_t rc = snd_pcm_writei(alsa_pcm, buffer, frames); | |
45 | if(rc < 0) { | |
46 | switch(rc) { | |
47 | case -EPIPE: | |
48 | if((err = snd_pcm_prepare(alsa_pcm))) | |
49 | fatal(0, "error calling snd_pcm_prepare: %d", err); | |
50 | return 0; | |
51 | case -EAGAIN: | |
52 | return 0; | |
53 | default: | |
54 | fatal(0, "error calling snd_pcm_writei: %d", (int)rc); | |
55 | } | |
56 | } | |
57 | return rc * uaudio_channels; | |
58 | } | |
59 | ||
60 | /** @brief Open the ALSA sound device */ | |
61 | static void alsa_open(void) { | |
62 | const char *device = uaudio_get("device"); | |
63 | int err; | |
64 | ||
65 | if(!device || !*device) | |
66 | device = "default"; | |
67 | if((err = snd_pcm_open(&alsa_pcm, | |
68 | device, | |
69 | SND_PCM_STREAM_PLAYBACK, | |
70 | 0))) | |
71 | fatal(0, "error from snd_pcm_open: %d", err); | |
72 | snd_pcm_hw_params_t *hwparams; | |
73 | snd_pcm_hw_params_alloca(&hwparams); | |
74 | if((err = snd_pcm_hw_params_any(alsa_pcm, hwparams)) < 0) | |
75 | fatal(0, "error from snd_pcm_hw_params_any: %d", err); | |
76 | if((err = snd_pcm_hw_params_set_access(alsa_pcm, hwparams, | |
77 | SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) | |
78 | fatal(0, "error from snd_pcm_hw_params_set_access: %d", err); | |
79 | int sample_format; | |
80 | if(uaudio_bits == 16) | |
81 | sample_format = uaudio_signed ? SND_PCM_FORMAT_S16 : SND_PCM_FORMAT_U16; | |
82 | else | |
83 | sample_format = uaudio_signed ? SND_PCM_FORMAT_S8 : SND_PCM_FORMAT_U8; | |
84 | if((err = snd_pcm_hw_params_set_format(alsa_pcm, hwparams, | |
85 | sample_format)) < 0) | |
86 | fatal(0, "error from snd_pcm_hw_params_set_format (%d): %d", | |
87 | sample_format, err); | |
88 | unsigned rate = uaudio_rate; | |
89 | if((err = snd_pcm_hw_params_set_rate_near(alsa_pcm, hwparams, &rate, 0)) < 0) | |
90 | fatal(0, "error from snd_pcm_hw_params_set_rate_near (%d): %d", | |
91 | rate, err); | |
92 | if((err = snd_pcm_hw_params_set_channels(alsa_pcm, hwparams, | |
93 | uaudio_channels)) < 0) | |
94 | fatal(0, "error from snd_pcm_hw_params_set_channels (%d): %d", | |
95 | uaudio_channels, err); | |
96 | if((err = snd_pcm_hw_params(alsa_pcm, hwparams)) < 0) | |
97 | fatal(0, "error calling snd_pcm_hw_params: %d", err); | |
98 | ||
99 | } | |
100 | ||
101 | static void alsa_activate(void) { | |
102 | uaudio_thread_activate(); | |
103 | } | |
104 | ||
105 | static void alsa_deactivate(void) { | |
106 | uaudio_thread_deactivate(); | |
107 | } | |
108 | ||
109 | static void alsa_start(uaudio_callback *callback, | |
110 | void *userdata) { | |
111 | if(uaudio_channels != 1 && uaudio_channels != 2) | |
112 | fatal(0, "asked for %d channels but only support 1 or 2", | |
113 | uaudio_channels); | |
114 | if(uaudio_bits != 8 && uaudio_bits != 16) | |
115 | fatal(0, "asked for %d bits/channel but only support 8 or 16", | |
116 | uaudio_bits); | |
117 | alsa_open(); | |
118 | uaudio_thread_start(callback, userdata, alsa_play, | |
119 | 32 / uaudio_sample_size, | |
120 | 4096 / uaudio_sample_size); | |
121 | } | |
122 | ||
123 | static void alsa_stop(void) { | |
124 | uaudio_thread_stop(); | |
125 | snd_pcm_close(alsa_pcm); | |
126 | alsa_pcm = 0; | |
127 | } | |
128 | ||
129 | const struct uaudio uaudio_alsa = { | |
130 | .name = "alsa", | |
131 | .options = alsa_options, | |
132 | .start = alsa_start, | |
133 | .stop = alsa_stop, | |
134 | .activate = alsa_activate, | |
135 | .deactivate = alsa_deactivate | |
136 | }; | |
137 | ||
138 | #endif | |
139 | ||
140 | /* | |
141 | Local Variables: | |
142 | c-basic-offset:2 | |
143 | comment-column:40 | |
144 | fill-column:79 | |
145 | indent-tabs-mode:nil | |
146 | End: | |
147 | */ |