1
/*
2
 * Copyright 2008-2011 Various Authors
3
 * Copyright 2004-2005 Timo Hirvonen
4
 *
5
 * sun.c by alex <pukpuk@gmx.de>
6
 *
7
 * This program is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU General Public License as
9
 * published by the Free Software Foundation; either version 2 of the
10
 * License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful, but
13
 * WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
19
 */
20
21
#include <sys/types.h>
22
#include <sys/ioctl.h>
23
#include <sys/audioio.h>
24
#include <sys/stat.h>
25
#include <errno.h>
26
#include <fcntl.h>
27
#include <string.h>
28
#include <unistd.h>
29
30
#include "op.h"
31
#include "sf.h"
32
#include "xmalloc.h"
33
34
static sample_format_t sun_sf;
35
static int sun_fd = -1;
36
37
static char *sun_audio_device = NULL;
38
39
static int sun_reset(void)
40
{
41
	if (ioctl(sun_fd, AUDIO_FLUSH, NULL) == -1)
42
		return -1;
43
44
	return 0;
45
}
46
47
static int sun_set_sf(sample_format_t sf)
48
{
49
	struct audio_info ainf;
50
51
	AUDIO_INITINFO(&ainf);
52
53
	sun_reset();
54
	sun_sf = sf;
55
56
	ainf.play.channels = sf_get_channels(sun_sf);
57
	ainf.play.sample_rate = sf_get_rate(sun_sf);
58
	ainf.play.pause = 0;
59
	ainf.mode = AUMODE_PLAY;
60
61
	switch (sf_get_bits(sun_sf)) {
62
	case 16:
63
		ainf.play.precision = 16;
64
		if (sf_get_signed(sun_sf)) {
65
			if (sf_get_bigendian(sun_sf))
66
				ainf.play.encoding = AUDIO_ENCODING_SLINEAR_BE;
67
			else
68
				ainf.play.encoding = AUDIO_ENCODING_SLINEAR_LE;
69
		} else {
70
			if (sf_get_bigendian(sun_sf))
71
				ainf.play.encoding = AUDIO_ENCODING_ULINEAR_BE;
72
			else
73
				ainf.play.encoding = AUDIO_ENCODING_ULINEAR_LE;
74
		}
75
		break;
76
	case 8:
77
		ainf.play.precision = 8;
78
		if (sf_get_signed(sun_sf))
79
			ainf.play.encoding = AUDIO_ENCODING_SLINEAR;
80
		else
81
			ainf.play.encoding = AUDIO_ENCODING_ULINEAR;
82
		break;
83
	default:
84
		return -1;
85
	}
86
87
	if (ioctl(sun_fd, AUDIO_SETINFO, &ainf) == -1)
88
		return -1;
89
90
	if (ioctl(sun_fd, AUDIO_GETINFO, &ainf) == -1)
91
		return -1;
92
93
	/* FIXME: check if sample rate is supported */
94
	return 0;
95
}
96
97
static int sun_device_exists(const char *dev)
98
{
99
	struct stat s;
100
101
	if (stat(dev, &s))
102
		return 0;
103
	return 1;
104
}
105
106
static int sun_init(void)
107
{
108
	const char *audio_dev = "/dev/audio";
109
110
	if (sun_audio_device != NULL) {
111
		if (sun_device_exists(sun_audio_device))
112
			return 0;
113
		free(sun_audio_device);
114
		sun_audio_device = NULL;
115
		return -1;
116
	}
117
	if (sun_device_exists(audio_dev)) {
118
		sun_audio_device = xstrdup(audio_dev);
119
		return 0;
120
	}
121
122
	return -1;
123
}
124
125
static int sun_exit(void)
126
{
127
	if (sun_audio_device != NULL) {
128
		free(sun_audio_device);
129
		sun_audio_device = NULL;
130
	}
131
132
	return 0;
133
}
134
135
static int sun_close(void)
136
{
137
	if (sun_fd != -1) {
138
		close(sun_fd);
139
		sun_fd = -1;
140
	}
141
142
	return 0;
143
}
144
145
static int sun_open(sample_format_t sf, const channel_position_t *channel_map)
146
{
147
	sun_fd = open(sun_audio_device, O_WRONLY);
148
	if (sun_fd == -1)
149
		return -1;
150
	if (sun_set_sf(sf) == -1) {
151
		sun_close();
152
		return -1;
153
	}
154
155
	return 0;
156
}
157
158
static int sun_write(const char *buf, int cnt)
159
{
160
	const char *t;
161
162
	cnt /= 4;
163
	cnt *= 4;
164
	t = buf;
165
	while (cnt > 0) {
166
		int rc = write(sun_fd, buf, cnt);
167
		if (rc == -1) {
168
			if (errno == EINTR)
169
				continue;
170
			else
171
				return rc;
172
		}
173
		buf += rc;
174
		cnt -= rc;
175
	}
176
177
	return (buf - t);
178
}
179
180
static int sun_pause(void)
181
{
182
	struct audio_info ainf;
183
184
	AUDIO_INITINFO(&ainf);
185
186
	ainf.play.pause = 1;
187
	if (ioctl(sun_fd, AUDIO_SETINFO, &ainf) == -1)
188
		return -1;
189
190
	return 0;
191
}
192
193
static int sun_unpause(void)
194
{
195
	struct audio_info ainf;
196
197
	AUDIO_INITINFO(&ainf);
198
199
	ainf.play.pause = 0;
200
	if (ioctl(sun_fd, AUDIO_SETINFO, &ainf) == -1)
201
		return -1;
202
203
	return 0;
204
}
205
206
static int sun_buffer_space(void)
207
{
208
	struct audio_info ainf;
209
	int sp;
210
211
	AUDIO_INITINFO(&ainf);
212
213
	if (ioctl(sun_fd, AUDIO_GETINFO, &ainf) == -1)
214
		return -1;
215
	sp = ainf.play.buffer_size;
216
217
	return sp;
218
}
219
220
static int op_sun_set_option(int key, const char *val)
221
{
222
	switch (key) {
223
	case 0:
224
		free(sun_audio_device);
225
		sun_audio_device = xstrdup(val);
226
		break;
227
	default:
228
		return -OP_ERROR_NOT_OPTION;
229
	}
230
231
	return 0;
232
}
233
234
static int op_sun_get_option(int key, char **val)
235
{
236
	switch (key) {
237
	case 0:
238
		if (sun_audio_device)
239
			*val = xstrdup(sun_audio_device);
240
		break;
241
	default:
242
		return -OP_ERROR_NOT_OPTION;
243
	}
244
245
	return 0;
246
}
247
248
const struct output_plugin_ops op_pcm_ops = {
249
	.init = sun_init,
250
	.exit = sun_exit,
251
	.open = sun_open,
252
	.close = sun_close,
253
	.write = sun_write,
254
	.pause = sun_pause,
255
	.unpause = sun_unpause,
256
	.buffer_space = sun_buffer_space,
257
	.set_option = op_sun_set_option,
258
	.get_option = op_sun_get_option
259
};
260
261
const char * const op_pcm_options[] = {
262
	"device",
263
	NULL
264
};
265
266
const int op_priority = 0;