oss_plugin.c 12.6 KB
Newer Older
1
/* the Music Player Daemon (MPD)
2
 * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
3 4
 * This project's homepage is: http://www.musicpd.org
 *
5
 * OSS audio output (c) 2004, 2005, 2006, 2007 by Eric Wong <eric@petta-tech.com>
6
 *                   and Warren Dukes <warren.dukes@gmail.com>
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

22
#include "../output_api.h"
23
#include "../mixer_api.h"
24

25
#include <glib.h>
26

27 28 29
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
30 31 32 33 34 35
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "oss"
36

37 38
#if defined(__OpenBSD__) || defined(__NetBSD__)
# include <soundcard.h>
Avuton Olrich's avatar
Avuton Olrich committed
39
#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
40
# include <sys/soundcard.h>
Avuton Olrich's avatar
Avuton Olrich committed
41
#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
42

Max Kellermann's avatar
Max Kellermann committed
43
#if G_BYTE_ORDER == G_BIG_ENDIAN
44 45 46
# define	AFMT_S16_MPD	 AFMT_S16_BE
#else
# define	AFMT_S16_MPD	 AFMT_S16_LE
47
#endif
48

49
typedef struct _OssData {
50
	int fd;
51
	const char *device;
52
	struct audio_format audio_format;
53
	int bitFormat;
Avuton Olrich's avatar
Avuton Olrich committed
54
	int *supported[3];
55
	int numSupported[3];
Avuton Olrich's avatar
Avuton Olrich committed
56
	int *unsupported[3];
57
	int numUnsupported[3];
58 59 60

	/** the mixer object associated with this output */
	struct mixer *mixer;
61 62
} OssData;

63 64 65 66 67 68 69 70 71 72 73 74 75
enum oss_support {
	OSS_SUPPORTED = 1,
	OSS_UNSUPPORTED = 0,
	OSS_UNKNOWN = -1,
};

enum oss_param {
	OSS_RATE = 0,
	OSS_CHANNELS = 1,
	OSS_BITS = 2,
};

static enum oss_param
76
getIndexForParam(unsigned param)
Avuton Olrich's avatar
Avuton Olrich committed
77
{
78
	enum oss_param idx = OSS_RATE;
Avuton Olrich's avatar
Avuton Olrich committed
79 80

	switch (param) {
81
	case SNDCTL_DSP_SPEED:
Max Kellermann's avatar
Max Kellermann committed
82
		idx = OSS_RATE;
83 84
		break;
	case SNDCTL_DSP_CHANNELS:
Max Kellermann's avatar
Max Kellermann committed
85
		idx = OSS_CHANNELS;
86 87
		break;
	case SNDCTL_DSP_SAMPLESIZE:
Max Kellermann's avatar
Max Kellermann committed
88
		idx = OSS_BITS;
89 90 91
		break;
	}

Max Kellermann's avatar
Max Kellermann committed
92
	return idx;
93 94
}

95
static int findSupportedParam(OssData * od, unsigned param, int val)
Avuton Olrich's avatar
Avuton Olrich committed
96
{
97
	int i;
98
	enum oss_param idx = getIndexForParam(param);
Avuton Olrich's avatar
Avuton Olrich committed
99

Max Kellermann's avatar
Max Kellermann committed
100 101
	for (i = 0; i < od->numSupported[idx]; i++) {
		if (od->supported[idx][i] == val)
Avuton Olrich's avatar
Avuton Olrich committed
102
			return 1;
103 104 105 106 107
	}

	return 0;
}

Max Kellermann's avatar
Max Kellermann committed
108
static int canConvert(int idx, int val)
Avuton Olrich's avatar
Avuton Olrich committed
109
{
Max Kellermann's avatar
Max Kellermann committed
110
	switch (idx) {
111
	case OSS_BITS:
Avuton Olrich's avatar
Avuton Olrich committed
112 113
		if (val != 16)
			return 0;
114 115
		break;
	case OSS_CHANNELS:
Avuton Olrich's avatar
Avuton Olrich committed
116 117
		if (val != 2)
			return 0;
118 119 120 121 122 123
		break;
	}

	return 1;
}

124
static int getSupportedParam(OssData * od, unsigned param, int val)
Avuton Olrich's avatar
Avuton Olrich committed
125
{
126
	int i;
127
	enum oss_param idx = getIndexForParam(param);
128 129 130
	int ret = -1;
	int least = val;
	int diff;
Avuton Olrich's avatar
Avuton Olrich committed
131

Max Kellermann's avatar
Max Kellermann committed
132 133
	for (i = 0; i < od->numSupported[idx]; i++) {
		diff = od->supported[idx][i] - val;
Avuton Olrich's avatar
Avuton Olrich committed
134 135 136
		if (diff < 0)
			diff = -diff;
		if (diff < least) {
Max Kellermann's avatar
Max Kellermann committed
137
			if (!canConvert(idx, od->supported[idx][i])) {
138 139 140
				continue;
			}
			least = diff;
Max Kellermann's avatar
Max Kellermann committed
141
			ret = od->supported[idx][i];
142 143 144 145 146 147
		}
	}

	return ret;
}

148
static int findUnsupportedParam(OssData * od, unsigned param, int val)
Avuton Olrich's avatar
Avuton Olrich committed
149
{
150
	int i;
151
	enum oss_param idx = getIndexForParam(param);
Avuton Olrich's avatar
Avuton Olrich committed
152

Max Kellermann's avatar
Max Kellermann committed
153 154
	for (i = 0; i < od->numUnsupported[idx]; i++) {
		if (od->unsupported[idx][i] == val)
Avuton Olrich's avatar
Avuton Olrich committed
155
			return 1;
156 157 158 159 160
	}

	return 0;
}

161
static void addSupportedParam(OssData * od, unsigned param, int val)
Avuton Olrich's avatar
Avuton Olrich committed
162
{
163
	enum oss_param idx = getIndexForParam(param);
164

Max Kellermann's avatar
Max Kellermann committed
165
	od->numSupported[idx]++;
166 167
	od->supported[idx] = g_realloc(od->supported[idx],
				       od->numSupported[idx] * sizeof(int));
Max Kellermann's avatar
Max Kellermann committed
168
	od->supported[idx][od->numSupported[idx] - 1] = val;
169 170
}

171
static void addUnsupportedParam(OssData * od, unsigned param, int val)
Avuton Olrich's avatar
Avuton Olrich committed
172
{
173
	enum oss_param idx = getIndexForParam(param);
174

Max Kellermann's avatar
Max Kellermann committed
175
	od->numUnsupported[idx]++;
176 177 178
	od->unsupported[idx] = g_realloc(od->unsupported[idx],
					 od->numUnsupported[idx] *
					 sizeof(int));
Max Kellermann's avatar
Max Kellermann committed
179
	od->unsupported[idx][od->numUnsupported[idx] - 1] = val;
180 181
}

182
static void removeSupportedParam(OssData * od, unsigned param, int val)
Avuton Olrich's avatar
Avuton Olrich committed
183
{
184
	int i;
185
	int j = 0;
186
	enum oss_param idx = getIndexForParam(param);
187

Max Kellermann's avatar
Max Kellermann committed
188 189
	for (i = 0; i < od->numSupported[idx] - 1; i++) {
		if (od->supported[idx][i] == val)
Avuton Olrich's avatar
Avuton Olrich committed
190
			j = 1;
Max Kellermann's avatar
Max Kellermann committed
191
		od->supported[idx][i] = od->supported[idx][i + j];
192 193
	}

Max Kellermann's avatar
Max Kellermann committed
194
	od->numSupported[idx]--;
195 196
	od->supported[idx] = g_realloc(od->supported[idx],
				       od->numSupported[idx] * sizeof(int));
197 198
}

199
static void removeUnsupportedParam(OssData * od, unsigned param, int val)
Avuton Olrich's avatar
Avuton Olrich committed
200
{
201
	int i;
202
	int j = 0;
203
	enum oss_param idx = getIndexForParam(param);
204

Max Kellermann's avatar
Max Kellermann committed
205 206
	for (i = 0; i < od->numUnsupported[idx] - 1; i++) {
		if (od->unsupported[idx][i] == val)
Avuton Olrich's avatar
Avuton Olrich committed
207
			j = 1;
Max Kellermann's avatar
Max Kellermann committed
208
		od->unsupported[idx][i] = od->unsupported[idx][i + j];
209 210
	}

Max Kellermann's avatar
Max Kellermann committed
211
	od->numUnsupported[idx]--;
212 213 214
	od->unsupported[idx] = g_realloc(od->unsupported[idx],
					 od->numUnsupported[idx] *
					 sizeof(int));
215 216
}

217
static enum oss_support
218
isSupportedParam(OssData * od, unsigned param, int val)
Avuton Olrich's avatar
Avuton Olrich committed
219 220 221 222 223
{
	if (findSupportedParam(od, param, val))
		return OSS_SUPPORTED;
	if (findUnsupportedParam(od, param, val))
		return OSS_UNSUPPORTED;
224 225 226
	return OSS_UNKNOWN;
}

227
static void supportParam(OssData * od, unsigned param, int val)
Avuton Olrich's avatar
Avuton Olrich committed
228
{
229
	enum oss_support supported = isSupportedParam(od, param, val);
230

Avuton Olrich's avatar
Avuton Olrich committed
231 232
	if (supported == OSS_SUPPORTED)
		return;
233

Avuton Olrich's avatar
Avuton Olrich committed
234
	if (supported == OSS_UNSUPPORTED) {
235 236 237 238 239 240
		removeUnsupportedParam(od, param, val);
	}

	addSupportedParam(od, param, val);
}

241
static void unsupportParam(OssData * od, unsigned param, int val)
Avuton Olrich's avatar
Avuton Olrich committed
242
{
243
	enum oss_support supported = isSupportedParam(od, param, val);
244

Avuton Olrich's avatar
Avuton Olrich committed
245 246
	if (supported == OSS_UNSUPPORTED)
		return;
247

Avuton Olrich's avatar
Avuton Olrich committed
248
	if (supported == OSS_SUPPORTED) {
249 250 251 252 253 254
		removeSupportedParam(od, param, val);
	}

	addUnsupportedParam(od, param, val);
}

Avuton Olrich's avatar
Avuton Olrich committed
255 256
static OssData *newOssData(void)
{
257
	OssData *ret = g_new(OssData, 1);
258

259 260
	ret->device = NULL;
	ret->fd = -1;
261

262 263 264 265 266 267 268 269 270 271 272 273 274 275
	ret->supported[OSS_RATE] = NULL;
	ret->supported[OSS_CHANNELS] = NULL;
	ret->supported[OSS_BITS] = NULL;
	ret->unsupported[OSS_RATE] = NULL;
	ret->unsupported[OSS_CHANNELS] = NULL;
	ret->unsupported[OSS_BITS] = NULL;

	ret->numSupported[OSS_RATE] = 0;
	ret->numSupported[OSS_CHANNELS] = 0;
	ret->numSupported[OSS_BITS] = 0;
	ret->numUnsupported[OSS_RATE] = 0;
	ret->numUnsupported[OSS_CHANNELS] = 0;
	ret->numUnsupported[OSS_BITS] = 0;

Avuton Olrich's avatar
Avuton Olrich committed
276 277 278 279
	supportParam(ret, SNDCTL_DSP_SPEED, 48000);
	supportParam(ret, SNDCTL_DSP_SPEED, 44100);
	supportParam(ret, SNDCTL_DSP_CHANNELS, 2);
	supportParam(ret, SNDCTL_DSP_SAMPLESIZE, 16);
280

281 282 283
	return ret;
}

Avuton Olrich's avatar
Avuton Olrich committed
284 285
static void freeOssData(OssData * od)
{
286 287 288 289 290 291
	g_free(od->supported[OSS_RATE]);
	g_free(od->supported[OSS_CHANNELS]);
	g_free(od->supported[OSS_BITS]);
	g_free(od->unsupported[OSS_RATE]);
	g_free(od->unsupported[OSS_CHANNELS]);
	g_free(od->unsupported[OSS_BITS]);
292

293
	mixer_free(od->mixer);
294

295
	g_free(od);
296 297
}

298 299 300 301 302 303
#define OSS_STAT_NO_ERROR 	0
#define OSS_STAT_NOT_CHAR_DEV	-1
#define OSS_STAT_NO_PERMS	-2
#define OSS_STAT_DOESN_T_EXIST	-3
#define OSS_STAT_OTHER		-4

304
static int oss_statDevice(const char *device, int *stErrno)
Avuton Olrich's avatar
Avuton Olrich committed
305
{
306
	struct stat st;
Avuton Olrich's avatar
Avuton Olrich committed
307 308 309

	if (0 == stat(device, &st)) {
		if (!S_ISCHR(st.st_mode)) {
310 311
			return OSS_STAT_NOT_CHAR_DEV;
		}
Avuton Olrich's avatar
Avuton Olrich committed
312
	} else {
313 314
		*stErrno = errno;

Avuton Olrich's avatar
Avuton Olrich committed
315
		switch (errno) {
316 317 318 319 320 321 322 323 324 325 326 327 328
		case ENOENT:
		case ENOTDIR:
			return OSS_STAT_DOESN_T_EXIST;
		case EACCES:
			return OSS_STAT_NO_PERMS;
		default:
			return OSS_STAT_OTHER;
		}
	}

	return 0;
}

329 330
static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" };

331
static bool oss_testDefault(void)
Avuton Olrich's avatar
Avuton Olrich committed
332
{
333
	int fd, i;
334

335
	for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) {
336
		if ((fd = open(default_devices[i], O_WRONLY)) >= 0) {
337
			close(fd);
338
			return true;
339
		}
340 341
		g_warning("Error opening OSS device \"%s\": %s\n",
			  default_devices[i], strerror(errno));
342 343
	}

344
	return false;
345
}
346

347
static void *oss_open_default(const struct config_param *param)
348 349
{
	int i;
350 351
	int err[G_N_ELEMENTS(default_devices)];
	int ret[G_N_ELEMENTS(default_devices)];
352

353
	for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) {
354 355
		ret[i] = oss_statDevice(default_devices[i], &err[i]);
		if (ret[i] == 0) {
356
			OssData *od = newOssData();
357
			od->device = default_devices[i];
358
			od->mixer = mixer_new(&oss_mixer, param);
359
			return od;
360
		}
361 362
	}

363
	if (param)
364 365
		g_warning("error trying to open specified OSS device"
			  " at line %i\n", param->line);
366
	else
367
		g_warning("error trying to open default OSS device\n");
368

369
	for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) {
370 371 372
		const char *dev = default_devices[i];
		switch(ret[i]) {
		case OSS_STAT_DOESN_T_EXIST:
373
			g_warning("%s not found\n", dev);
374 375
			break;
		case OSS_STAT_NOT_CHAR_DEV:
376
			g_warning("%s is not a character device\n", dev);
377 378
			break;
		case OSS_STAT_NO_PERMS:
379
			g_warning("%s: permission denied\n", dev);
380 381
			break;
		default:
382 383
			g_warning("Error accessing %s: %s\n",
				  dev, strerror(err[i]));
384 385 386
		}
	}
	exit(EXIT_FAILURE);
387
	return NULL; /* some compilers can be dumb... */
388 389
}

390
static void *
391
oss_initDriver(G_GNUC_UNUSED const struct audio_format *audio_format,
392
	       const struct config_param *param)
Avuton Olrich's avatar
Avuton Olrich committed
393
{
Max Kellermann's avatar
Max Kellermann committed
394 395 396 397
	const char *device = config_get_block_string(param, "device", NULL);
	if (device != NULL) {
		OssData *od = newOssData();
		od->device = device;
398
		od->mixer = mixer_new(&oss_mixer, param);
Max Kellermann's avatar
Max Kellermann committed
399
		return od;
400
	}
Max Kellermann's avatar
Max Kellermann committed
401

402
	return oss_open_default(param);
403 404
}

405
static void oss_finishDriver(void *data)
Avuton Olrich's avatar
Avuton Olrich committed
406
{
407
	OssData *od = data;
408 409 410 411

	freeOssData(od);
}

412 413 414 415 416 417 418 419
static struct mixer *
oss_get_mixer(void *data)
{
	OssData *od = data;

	return od->mixer;
}

420
static int setParam(OssData * od, unsigned param, int *value)
Avuton Olrich's avatar
Avuton Olrich committed
421
{
422 423
	int val = *value;
	int copy;
424
	enum oss_support supported = isSupportedParam(od, param, val);
425 426

	do {
Avuton Olrich's avatar
Avuton Olrich committed
427
		if (supported == OSS_UNSUPPORTED) {
428
			val = getSupportedParam(od, param, val);
Avuton Olrich's avatar
Avuton Olrich committed
429 430
			if (copy < 0)
				return -1;
431 432
		}
		copy = val;
Avuton Olrich's avatar
Avuton Olrich committed
433
		if (ioctl(od->fd, param, &copy)) {
434 435
			unsupportParam(od, param, val);
			supported = OSS_UNSUPPORTED;
Avuton Olrich's avatar
Avuton Olrich committed
436 437
		} else {
			if (supported == OSS_UNKNOWN) {
438 439 440 441 442
				supportParam(od, param, val);
				supported = OSS_SUPPORTED;
			}
			val = copy;
		}
Avuton Olrich's avatar
Avuton Olrich committed
443
	} while (supported == OSS_UNSUPPORTED);
444 445 446 447 448 449

	*value = val;

	return 0;
}

450 451
static void oss_close(OssData * od)
{
Avuton Olrich's avatar
Avuton Olrich committed
452 453
	if (od->fd >= 0)
		while (close(od->fd) && errno == EINTR) ;
454 455 456
	od->fd = -1;
}

457
static bool oss_open(OssData *od)
Avuton Olrich's avatar
Avuton Olrich committed
458
{
459
	int tmp;
460

Avuton Olrich's avatar
Avuton Olrich committed
461
	if ((od->fd = open(od->device, O_WRONLY)) < 0) {
462 463
		g_warning("Error opening OSS device \"%s\": %s\n", od->device,
			  strerror(errno));
464 465 466
		goto fail;
	}

467 468
	tmp = od->audio_format.channels;
	if (setParam(od, SNDCTL_DSP_CHANNELS, &tmp)) {
469 470 471
		g_warning("OSS device \"%s\" does not support %u channels: %s\n",
			  od->device, od->audio_format.channels,
			  strerror(errno));
472 473
		goto fail;
	}
474
	od->audio_format.channels = tmp;
475

476 477
	tmp = od->audio_format.sample_rate;
	if (setParam(od, SNDCTL_DSP_SPEED, &tmp)) {
478 479 480
		g_warning("OSS device \"%s\" does not support %u Hz audio: %s\n",
			  od->device, od->audio_format.sample_rate,
			  strerror(errno));
481 482
		goto fail;
	}
483
	od->audio_format.sample_rate = tmp;
484

485
	switch (od->audio_format.bits) {
486 487 488 489 490
	case 8:
		tmp = AFMT_S8;
		break;
	case 16:
		tmp = AFMT_S16_MPD;
491 492 493 494 495 496 497
		break;

	default:
		/* not supported by OSS - fall back to 16 bit */
		od->audio_format.bits = 16;
		tmp = AFMT_S16_MPD;
		break;
498 499
	}

Avuton Olrich's avatar
Avuton Olrich committed
500
	if (setParam(od, SNDCTL_DSP_SAMPLESIZE, &tmp)) {
501 502
		g_warning("OSS device \"%s\" does not support %u bit audio: %s\n",
			  od->device, tmp, strerror(errno));
503 504
		goto fail;
	}
505

506
	return true;
507

508
fail:
509
	oss_close(od);
510
	return false;
511 512
}

513 514
static bool
oss_openDevice(void *data, struct audio_format *audioFormat)
515
{
516
	bool ret;
517
	OssData *od = data;
518

519
	od->audio_format = *audioFormat;
520

521 522 523
	ret = oss_open(od);
	if (!ret)
		return false;
524

525
	*audioFormat = od->audio_format;
526

527 528 529 530
	g_debug("device \"%s\" will be playing %u bit %u channel audio at "
		"%u Hz\n", od->device,
		od->audio_format.bits, od->audio_format.channels,
		od->audio_format.sample_rate);
531

532
	mixer_open(od->mixer);
533

534
	return ret;
535 536
}

537
static void oss_closeDevice(void *data)
Avuton Olrich's avatar
Avuton Olrich committed
538
{
539
	OssData *od = data;
540

541
	oss_close(od);
542
	mixer_close(od->mixer);
543 544
}

545
static void oss_dropBufferedAudio(void *data)
Avuton Olrich's avatar
Avuton Olrich committed
546
{
547
	OssData *od = data;
548

Avuton Olrich's avatar
Avuton Olrich committed
549
	if (od->fd >= 0) {
550
		ioctl(od->fd, SNDCTL_DSP_RESET, 0);
551
		oss_close(od);
552 553 554
	}
}

555
static size_t
556
oss_playAudio(void *data, const void *chunk, size_t size)
557
{
558
	OssData *od = data;
559
	ssize_t ret;
560

561
	/* reopen the device since it was closed by dropBufferedAudio */
562
	if (od->fd < 0 && !oss_open(od))
563
		return false;
564

565
	while (true) {
566
		ret = write(od->fd, chunk, size);
567 568 569 570
		if (ret > 0)
			return (size_t)ret;

		if (ret < 0 && errno != EINTR) {
571 572
			g_warning("closing oss device \"%s\" due to write error: "
				  "%s\n", od->device, strerror(errno));
573
			return 0;
574 575 576 577
		}
	}
}

578
const struct audio_output_plugin ossPlugin = {
579 580 581 582
	.name = "oss",
	.test_default_device = oss_testDefault,
	.init = oss_initDriver,
	.finish = oss_finishDriver,
583
	.get_mixer = oss_get_mixer,
584 585 586 587
	.open = oss_openDevice,
	.play = oss_playAudio,
	.cancel = oss_dropBufferedAudio,
	.close = oss_closeDevice,
588
};