1
/*
2
 * Copyright 2008-2011 Various Authors
3
 * Copyright 2008 Jonathan Kleinehellefort
4
 * Copyright 2004-2005 Timo Hirvonen
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License as
8
 * published by the Free Software Foundation; either version 2 of the
9
 * License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful, but
12
 * WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
 * General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
18
 */
19
20
/*
21
 * snd_pcm_state_t:
22
 * 
23
 * Open
24
 * SND_PCM_STATE_OPEN = 0,
25
 * 
26
 * Setup installed 
27
 * SND_PCM_STATE_SETUP = 1,
28
 * 
29
 * Ready to start
30
 * SND_PCM_STATE_PREPARED = 2,
31
 * 
32
 * Running
33
 * SND_PCM_STATE_RUNNING = 3,
34
 * 
35
 * Stopped: underrun (playback) or overrun (capture) detected
36
 * SND_PCM_STATE_XRUN = 4,
37
 * 
38
 * Draining: running (playback) or stopped (capture)
39
 * SND_PCM_STATE_DRAINING = 5,
40
 * 
41
 * Paused
42
 * SND_PCM_STATE_PAUSED = 6,
43
 * 
44
 * Hardware is suspended
45
 * SND_PCM_STATE_SUSPENDED = 7,
46
 * 
47
 * Hardware is disconnected
48
 * SND_PCM_STATE_DISCONNECTED = 8,
49
 */
50
51
#include "op.h"
52
#include "utils.h"
53
#include "xmalloc.h"
54
#include "sf.h"
55
#include "debug.h"
56
57
#define ALSA_PCM_NEW_HW_PARAMS_API
58
#define ALSA_PCM_NEW_SW_PARAMS_API
59
60
#include <alsa/asoundlib.h>
61
62
static sample_format_t alsa_sf;
63
static snd_pcm_t *alsa_handle;
64
static snd_pcm_format_t alsa_fmt;
65
static int alsa_can_pause;
66
static snd_pcm_status_t *status;
67
68
/* bytes (bits * channels / 8) */
69
static int alsa_frame_size;
70
71
/* configuration */
72
static char *alsa_dsp_device = NULL;
73
74
#if 0
75
#define debug_ret(func, ret) \
76
	d_print("%s returned %d %s\n", func, ret, ret < 0 ? snd_strerror(ret) : "")
77
#else
78
#define debug_ret(func, ret) do { } while (0)
79
#endif
80
81
static int alsa_error_to_op_error(int err)
82
{
83
	if (!err)
84
		return OP_ERROR_SUCCESS;
85
	err = -err;
86
	if (err < SND_ERROR_BEGIN) {
87
		errno = err;
88
		return -OP_ERROR_ERRNO;
89
	}
90
	return -OP_ERROR_INTERNAL;
91
}
92
93
/* we don't want error messages to stderr */
94
static void error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...)
95
{
96
}
97
98
static int op_alsa_init(void)
99
{
100
	int rc;
101
102
	snd_lib_error_set_handler(error_handler);
103
104
	if (alsa_dsp_device == NULL)
105
		alsa_dsp_device = xstrdup("default");
106
	rc = snd_pcm_status_malloc(&status);
107
	if (rc < 0) {
108
		free(alsa_dsp_device);
109
		alsa_dsp_device = NULL;
110
		errno = ENOMEM;
111
		return -OP_ERROR_ERRNO;
112
	}
113
	return OP_ERROR_SUCCESS;
114
}
115
116
static int op_alsa_exit(void)
117
{
118
	snd_pcm_status_free(status);
119
	free(alsa_dsp_device);
120
	alsa_dsp_device = NULL;
121
	return OP_ERROR_SUCCESS;
122
}
123
124
/* randomize hw params */
125
static int alsa_set_hw_params(void)
126
{
127
	snd_pcm_hw_params_t *hwparams = NULL;
128
	const char *cmd;
129
	unsigned int rate;
130
	int rc, dir;
131
132
	snd_pcm_hw_params_malloc(&hwparams);
133
134
	cmd = "snd_pcm_hw_params_any";
135
	rc = snd_pcm_hw_params_any(alsa_handle, hwparams);
136
	if (rc < 0)
137
		goto error;
138
139
	alsa_can_pause = snd_pcm_hw_params_can_pause(hwparams);
140
	d_print("can pause = %d\n", alsa_can_pause);
141
142
	cmd = "snd_pcm_hw_params_set_access";
143
	rc = snd_pcm_hw_params_set_access(alsa_handle, hwparams,
144
			SND_PCM_ACCESS_RW_INTERLEAVED);
145
	if (rc < 0)
146
		goto error;
147
148
	alsa_fmt = snd_pcm_build_linear_format(sf_get_bits(alsa_sf), sf_get_bits(alsa_sf),
149
			sf_get_signed(alsa_sf) ? 0 : 1,
150
			sf_get_bigendian(alsa_sf));
151
	cmd = "snd_pcm_hw_params_set_format";
152
	rc = snd_pcm_hw_params_set_format(alsa_handle, hwparams, alsa_fmt);
153
	if (rc < 0)
154
		goto error;
155
156
	cmd = "snd_pcm_hw_params_set_channels";
157
	rc = snd_pcm_hw_params_set_channels(alsa_handle, hwparams, sf_get_channels(alsa_sf));
158
	if (rc < 0)
159
		goto error;
160
161
	cmd = "snd_pcm_hw_params_set_rate";
162
	rate = sf_get_rate(alsa_sf);
163
	dir = 0;
164
	rc = snd_pcm_hw_params_set_rate_near(alsa_handle, hwparams, &rate, &dir);
165
	if (rc < 0)
166
		goto error;
167
	d_print("rate=%d\n", rate);
168
169
	cmd = "snd_pcm_hw_params";
170
	rc = snd_pcm_hw_params(alsa_handle, hwparams);
171
	if (rc < 0)
172
		goto error;
173
	goto out;
174
error:
175
	d_print("%s: error: %s\n", cmd, snd_strerror(rc));
176
out:
177
	snd_pcm_hw_params_free(hwparams);
178
	return rc;
179
}
180
181
static int op_alsa_open(sample_format_t sf, const channel_position_t *channel_map)
182
{
183
	int rc;
184
185
	alsa_sf = sf;
186
	alsa_frame_size = sf_get_frame_size(alsa_sf);
187
188
	rc = snd_pcm_open(&alsa_handle, alsa_dsp_device, SND_PCM_STREAM_PLAYBACK, 0);
189
	if (rc < 0)
190
		goto error;
191
192
	rc = alsa_set_hw_params();
193
	if (rc)
194
		goto close_error;
195
196
	rc = snd_pcm_prepare(alsa_handle);
197
	if (rc < 0)
198
		goto close_error;
199
	return OP_ERROR_SUCCESS;
200
close_error:
201
	snd_pcm_close(alsa_handle);
202
error:
203
	return alsa_error_to_op_error(rc);
204
}
205
206
static int op_alsa_write(const char *buffer, int count);
207
208
static int op_alsa_close(void)
209
{
210
	int rc;
211
212
	rc = snd_pcm_drain(alsa_handle);
213
	debug_ret("snd_pcm_drain", rc);
214
215
	rc = snd_pcm_close(alsa_handle);
216
	debug_ret("snd_pcm_close", rc);
217
	return alsa_error_to_op_error(rc);
218
}
219
220
static int op_alsa_drop(void)
221
{
222
	int rc;
223
224
	rc = snd_pcm_drop(alsa_handle);
225
	debug_ret("snd_pcm_drop", rc);
226
227
	rc = snd_pcm_prepare(alsa_handle);
228
	debug_ret("snd_pcm_prepare", rc);
229
230
	/* drop set state to SETUP
231
	 * prepare set state to PREPARED
232
	 *
233
	 * so if old state was PAUSED we can't UNPAUSE (see op_alsa_unpause)
234
	 */
235
	return alsa_error_to_op_error(rc);
236
}
237
238
static int op_alsa_write(const char *buffer, int count)
239
{
240
	int rc, len;
241
	int recovered = 0;
242
243
	len = count / alsa_frame_size;
244
again:
245
	rc = snd_pcm_writei(alsa_handle, buffer, len);
246
	if (rc < 0) {
247
		// rc _should_ be either -EBADFD, -EPIPE or -ESTRPIPE
248
		if (!recovered && (rc == -EINTR || rc == -EPIPE || rc == -ESTRPIPE)) {
249
			d_print("snd_pcm_writei failed: %s, trying to recover\n",
250
					snd_strerror(rc));
251
			recovered++;
252
			// this handles -EINTR, -EPIPE and -ESTRPIPE
253
			// for other errors it just returns the error code
254
			rc = snd_pcm_recover(alsa_handle, rc, 1);
255
			if (!rc)
256
				goto again;
257
		}
258
259
		/* this handles EAGAIN too which is not critical error */
260
		return alsa_error_to_op_error(rc);
261
	}
262
263
	rc *= alsa_frame_size;
264
	return rc;
265
}
266
267
static int op_alsa_buffer_space(void)
268
{
269
	int rc;
270
	snd_pcm_sframes_t f;
271
272
	f = snd_pcm_avail_update(alsa_handle);
273
	while (f < 0) {
274
		d_print("snd_pcm_avail_update failed: %s, trying to recover\n",
275
			snd_strerror(f));
276
		rc = snd_pcm_recover(alsa_handle, f, 1);
277
		if (rc < 0) {
278
			d_print("recovery failed: %s\n", snd_strerror(rc));
279
			return alsa_error_to_op_error(rc);
280
		}
281
		f = snd_pcm_avail_update(alsa_handle);
282
	}
283
284
	return f * alsa_frame_size;
285
}
286
287
static int op_alsa_pause(void)
288
{
289
	int rc = 0;
290
	if (alsa_can_pause) {
291
		snd_pcm_state_t state = snd_pcm_state(alsa_handle);
292
		if (state == SND_PCM_STATE_PREPARED) {
293
			// state is PREPARED -> no need to pause
294
		} else if (state == SND_PCM_STATE_RUNNING) {
295
			// state is RUNNING - > pause
296
297
			// infinite timeout
298
			rc = snd_pcm_wait(alsa_handle, -1);
299
			debug_ret("snd_pcm_wait", rc);
300
301
			rc = snd_pcm_pause(alsa_handle, 1);
302
			debug_ret("snd_pcm_pause", rc);
303
		} else {
304
			d_print("error: state is not RUNNING or PREPARED\n");
305
			rc = -OP_ERROR_INTERNAL;
306
		}
307
	} else {
308
		rc = snd_pcm_drop(alsa_handle);
309
		debug_ret("snd_pcm_drop", rc);
310
	}
311
	return alsa_error_to_op_error(rc);
312
}
313
314
static int op_alsa_unpause(void)
315
{
316
	int rc = 0;
317
	if (alsa_can_pause) {
318
		snd_pcm_state_t state = snd_pcm_state(alsa_handle);
319
		if (state == SND_PCM_STATE_PREPARED) {
320
			// state is PREPARED -> no need to unpause
321
		} else if (state == SND_PCM_STATE_PAUSED) {
322
			// state is PAUSED -> unpause
323
324
			// infinite timeout
325
			rc = snd_pcm_wait(alsa_handle, -1);
326
			debug_ret("snd_pcm_wait", rc);
327
328
			rc = snd_pcm_pause(alsa_handle, 0);
329
			debug_ret("snd_pcm_pause", rc);
330
		} else {
331
			d_print("error: state is not PAUSED nor PREPARED\n");
332
			rc = -OP_ERROR_INTERNAL;
333
		}
334
	} else {
335
		rc = snd_pcm_prepare(alsa_handle);
336
		debug_ret("snd_pcm_prepare", rc);
337
	}
338
	return alsa_error_to_op_error(rc);
339
}
340
341
static int op_alsa_set_option(int key, const char *val)
342
{
343
	switch (key) {
344
	case 0:
345
		free(alsa_dsp_device);
346
		alsa_dsp_device = xstrdup(val);
347
		break;
348
	default:
349
		return -OP_ERROR_NOT_OPTION;
350
	}
351
	return OP_ERROR_SUCCESS;
352
}
353
354
static int op_alsa_get_option(int key, char **val)
355
{
356
	switch (key) {
357
	case 0:
358
		if (alsa_dsp_device)
359
			*val = xstrdup(alsa_dsp_device);
360
		break;
361
	default:
362
		return -OP_ERROR_NOT_OPTION;
363
	}
364
	return OP_ERROR_SUCCESS;
365
}
366
367
const struct output_plugin_ops op_pcm_ops = {
368
	.init = op_alsa_init,
369
	.exit = op_alsa_exit,
370
	.open = op_alsa_open,
371
	.close = op_alsa_close,
372
	.drop = op_alsa_drop,
373
	.write = op_alsa_write,
374
	.buffer_space = op_alsa_buffer_space,
375
	.pause = op_alsa_pause,
376
	.unpause = op_alsa_unpause,
377
	.set_option = op_alsa_set_option,
378
	.get_option = op_alsa_get_option
379
};
380
381
const char * const op_pcm_options[] = {
382
	"device",
383
	NULL
384
};
385
386
const int op_priority = 0;