1
/*
2
 * Copyright 2008-2011 Various Authors
3
 * Copyright 2004-2005 Timo Hirvonen
4
 *
5
 * This program is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU General Public License as
7
 * published by the Free Software Foundation; either version 2 of the
8
 * License, or (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful, but
11
 * WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
 * 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
19
#include "op.h"
20
#include "sf.h"
21
#include "xmalloc.h"
22
#include "debug.h"
23
24
#if defined(__OpenBSD__)
25
#include <soundcard.h>
26
#else
27
#include <sys/soundcard.h>
28
#endif
29
#include <sys/ioctl.h>
30
#include <sys/types.h>
31
#include <sys/stat.h>
32
#include <unistd.h>
33
#include <fcntl.h>
34
35
static sample_format_t oss_sf;
36
static int oss_fd = -1;
37
38
/* configuration */
39
static char *oss_dsp_device = NULL;
40
41
static int oss_close(void);
42
43
static int oss_reset(void)
44
{
45
	if (fcntl(oss_fd, SNDCTL_DSP_RESET, 0) == -1) {
46
		return -1;
47
	}
48
	return 0;
49
}
50
51
/* defined only in OSSv4, but seem to work in OSSv3 (Linux) */
52
#ifndef AFMT_S32_LE
53
#define AFMT_S32_LE	0x00001000
54
#endif
55
#ifndef AFMT_S32_BE
56
#define AFMT_S32_BE	0x00002000
57
#endif
58
#ifndef AFMT_S24_PACKED
59
#define AFMT_S24_PACKED	0x00040000
60
#endif
61
62
static int oss_set_sf(sample_format_t sf)
63
{
64
	int tmp, log2_fragment_size, nr_fragments, bytes_per_second;
65
66
	oss_reset();
67
	oss_sf = sf;
68
69
#ifdef SNDCTL_DSP_CHANNELS
70
	tmp = sf_get_channels(oss_sf);
71
	if (ioctl(oss_fd, SNDCTL_DSP_CHANNELS, &tmp) == -1)
72
		return -1;
73
#else
74
	tmp = sf_get_channels(oss_sf) - 1;
75
	if (ioctl(oss_fd, SNDCTL_DSP_STEREO, &tmp) == -1)
76
		return -1;
77
#endif
78
79
	if (sf_get_bits(oss_sf) == 16) {
80
		if (sf_get_signed(oss_sf)) {
81
			if (sf_get_bigendian(oss_sf)) {
82
				tmp = AFMT_S16_BE;
83
			} else {
84
				tmp = AFMT_S16_LE;
85
			}
86
		} else {
87
			if (sf_get_bigendian(oss_sf)) {
88
				tmp = AFMT_U16_BE;
89
			} else {
90
				tmp = AFMT_U16_LE;
91
			}
92
		}
93
	} else if (sf_get_bits(oss_sf) == 8) {
94
		if (sf_get_signed(oss_sf)) {
95
			tmp = AFMT_S8;
96
		} else {
97
			tmp = AFMT_U8;
98
		}
99
	} else if (sf_get_bits(oss_sf) == 32 && sf_get_signed(oss_sf)) {
100
		if (sf_get_bigendian(oss_sf)) {
101
			tmp = AFMT_S32_BE;
102
		} else {
103
			tmp = AFMT_S32_LE;
104
		}
105
	} else if (sf_get_bits(oss_sf) == 24 && sf_get_signed(oss_sf) && !sf_get_bigendian(oss_sf)) {
106
		tmp = AFMT_S24_PACKED;
107
	} else {
108
		d_print("unsupported sample format: %c%u_%s\n",
109
			sf_get_signed(oss_sf) ? 'S' : 'U', sf_get_bits(oss_sf),
110
			sf_get_bigendian(oss_sf) ? "BE" : "LE");
111
		return -1;
112
	}
113
	if (ioctl(oss_fd, SNDCTL_DSP_SAMPLESIZE, &tmp) == -1)
114
		return -1;
115
116
	tmp = sf_get_rate(oss_sf);
117
	if (ioctl(oss_fd, SNDCTL_DSP_SPEED, &tmp) == -1)
118
		return -1;
119
120
	bytes_per_second = sf_get_second_size(oss_sf);
121
	log2_fragment_size = 0;
122
	while (1 << log2_fragment_size < bytes_per_second / 25)
123
		log2_fragment_size++;
124
	log2_fragment_size--;
125
	nr_fragments = 32;
126
127
	/* bits 0..15 = size of fragment, 16..31 = log2(number of fragments) */
128
	tmp = (nr_fragments << 16) + log2_fragment_size;
129
	if (ioctl(oss_fd, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
130
		return -1;
131
	return 0;
132
}
133
134
static int oss_device_exists(const char *device)
135
{
136
	struct stat s;
137
138
	if (stat(device, &s))
139
		return 0;
140
	return 1;
141
}
142
143
static int oss_init(void)
144
{
145
	const char *new_dsp_dev = "/dev/sound/dsp";
146
	const char *dsp_dev = "/dev/dsp";
147
148
	if (oss_dsp_device) {
149
		if (oss_device_exists(oss_dsp_device))
150
			return 0;
151
		free(oss_dsp_device);
152
		oss_dsp_device = NULL;
153
		return -1;
154
	}
155
	if (oss_device_exists(new_dsp_dev)) {
156
		oss_dsp_device = xstrdup(new_dsp_dev);
157
		return 0;
158
	}
159
	if (oss_device_exists(dsp_dev)) {
160
		oss_dsp_device = xstrdup(dsp_dev);
161
		return 0;
162
	}
163
	return -1;
164
}
165
166
static int oss_exit(void)
167
{
168
	free(oss_dsp_device);
169
	oss_dsp_device = NULL;
170
	return 0;
171
}
172
173
static int oss_open(sample_format_t sf, const channel_position_t *channel_map)
174
{
175
	int oss_version = 0;
176
	oss_fd = open(oss_dsp_device, O_WRONLY);
177
	if (oss_fd == -1)
178
		return -1;
179
	ioctl(oss_fd, OSS_GETVERSION, &oss_version);
180
	d_print("oss version: %#08x\n", oss_version);
181
	if (oss_set_sf(sf) == -1) {
182
		oss_close();
183
		return -1;
184
	}
185
	return 0;
186
}
187
188
static int oss_close(void)
189
{
190
	close(oss_fd);
191
	oss_fd = -1;
192
	return 0;
193
}
194
195
static int oss_write(const char *buffer, int count)
196
{
197
	int rc;
198
199
	count -= count % sf_get_frame_size(oss_sf);
200
	rc = write(oss_fd, buffer, count);
201
	return rc;
202
}
203
204
static int oss_pause(void)
205
{
206
	if (ioctl(oss_fd, SNDCTL_DSP_POST, NULL) == -1)
207
		return -1;
208
	return 0;
209
}
210
211
static int oss_unpause(void)
212
{
213
	return 0;
214
}
215
216
static int oss_buffer_space(void)
217
{
218
	audio_buf_info info;
219
	int space;
220
221
	if (ioctl(oss_fd, SNDCTL_DSP_GETOSPACE, &info) == -1)
222
		return -1;
223
	space = (info.fragments - 1) * info.fragsize;
224
	return space;
225
}
226
227
static int op_oss_set_option(int key, const char *val)
228
{
229
	switch (key) {
230
	case 0:
231
		free(oss_dsp_device);
232
		oss_dsp_device = xstrdup(val);
233
		break;
234
	default:
235
		return -OP_ERROR_NOT_OPTION;
236
	}
237
	return 0;
238
}
239
240
static int op_oss_get_option(int key, char **val)
241
{
242
	switch (key) {
243
	case 0:
244
		if (oss_dsp_device)
245
			*val = xstrdup(oss_dsp_device);
246
		break;
247
	default:
248
		return -OP_ERROR_NOT_OPTION;
249
	}
250
	return 0;
251
}
252
253
const struct output_plugin_ops op_pcm_ops = {
254
	.init = oss_init,
255
	.exit = oss_exit,
256
	.open = oss_open,
257
	.close = oss_close,
258
	.write = oss_write,
259
	.pause = oss_pause,
260
	.unpause = oss_unpause,
261
	.buffer_space = oss_buffer_space,
262
	.set_option = op_oss_set_option,
263
	.get_option = op_oss_get_option
264
};
265
266
const char * const op_pcm_options[] = {
267
	"device",
268
	NULL
269
};
270
271
const int op_priority = 1;