OssOutputPlugin.cxx 16 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2014 The Music Player Daemon Project
3
 * http://www.musicpd.org
4 5 6 7 8 9 10 11 12 13
 *
 * 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.
14 15 16 17
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 19
 */

20
#include "config.h"
21
#include "OssOutputPlugin.hxx"
22
#include "../OutputAPI.hxx"
Max Kellermann's avatar
Max Kellermann committed
23
#include "mixer/MixerList.hxx"
24
#include "system/fd_util.h"
25
#include "util/ConstBuffer.hxx"
26 27
#include "util/Error.hxx"
#include "util/Domain.hxx"
28
#include "util/Macros.hxx"
29
#include "system/ByteOrder.hxx"
30
#include "Log.hxx"
31

32 33 34
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
35 36 37
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
38
#include <assert.h>
39

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

46 47 48 49 50 51 52 53 54
/* We got bug reports from FreeBSD users who said that the two 24 bit
   formats generate white noise on FreeBSD, but 32 bit works.  This is
   a workaround until we know what exactly is expected by the kernel
   audio drivers. */
#ifndef __linux__
#undef AFMT_S24_PACKED
#undef AFMT_S24_NE
#endif

55
#ifdef AFMT_S24_PACKED
56 57
#include "pcm/PcmExport.hxx"
#include "util/Manual.hxx"
58 59
#endif

60
struct OssOutput {
61
	AudioOutput base;
62

63
#ifdef AFMT_S24_PACKED
64
	Manual<PcmExport> pcm_export;
65 66
#endif

67
	int fd;
68
	const char *device;
69

70 71 72 73
	/**
	 * The current input audio format.  This is needed to reopen
	 * the device after cancel().
	 */
74
	AudioFormat audio_format;
75 76 77 78 79 80

	/**
	 * The current OSS audio format.  This is needed to reopen the
	 * device after cancel().
	 */
	int oss_format;
81

82 83 84
	OssOutput()
		:base(oss_output_plugin),
		 fd(-1), device(nullptr) {}
85

86
	bool Initialize(const config_param &param, Error &error_r) {
87
		return base.Configure(param, error_r);
88
	}
89 90
};

91
static constexpr Domain oss_output_domain("oss_output");
92

93 94 95 96 97 98 99
enum oss_stat {
	OSS_STAT_NO_ERROR = 0,
	OSS_STAT_NOT_CHAR_DEV = -1,
	OSS_STAT_NO_PERMS = -2,
	OSS_STAT_DOESN_T_EXIST = -3,
	OSS_STAT_OTHER = -4,
};
100

101
static enum oss_stat
Max Kellermann's avatar
Max Kellermann committed
102
oss_stat_device(const char *device, int *errno_r)
Avuton Olrich's avatar
Avuton Olrich committed
103
{
104
	struct stat st;
Avuton Olrich's avatar
Avuton Olrich committed
105 106 107

	if (0 == stat(device, &st)) {
		if (!S_ISCHR(st.st_mode)) {
108 109
			return OSS_STAT_NOT_CHAR_DEV;
		}
Avuton Olrich's avatar
Avuton Olrich committed
110
	} else {
Max Kellermann's avatar
Max Kellermann committed
111
		*errno_r = errno;
112

Avuton Olrich's avatar
Avuton Olrich committed
113
		switch (errno) {
114 115 116 117 118 119 120 121 122 123
		case ENOENT:
		case ENOTDIR:
			return OSS_STAT_DOESN_T_EXIST;
		case EACCES:
			return OSS_STAT_NO_PERMS;
		default:
			return OSS_STAT_OTHER;
		}
	}

124
	return OSS_STAT_NO_ERROR;
125 126
}

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

Max Kellermann's avatar
Max Kellermann committed
129 130
static bool
oss_output_test_default_device(void)
Avuton Olrich's avatar
Avuton Olrich committed
131
{
132
	int fd, i;
133

134
	for (i = ARRAY_SIZE(default_devices); --i >= 0; ) {
135
		fd = open_cloexec(default_devices[i], O_WRONLY, 0);
136 137

		if (fd >= 0) {
138
			close(fd);
139
			return true;
140
		}
141 142 143 144

		FormatErrno(oss_output_domain,
			    "Error opening OSS device \"%s\"",
			    default_devices[i]);
145 146
	}

147
	return false;
148
}
149

150
static AudioOutput *
151
oss_open_default(Error &error)
152
{
153 154
	int err[ARRAY_SIZE(default_devices)];
	enum oss_stat ret[ARRAY_SIZE(default_devices)];
155

156
	const config_param empty;
157
	for (int i = ARRAY_SIZE(default_devices); --i >= 0; ) {
Max Kellermann's avatar
Max Kellermann committed
158
		ret[i] = oss_stat_device(default_devices[i], &err[i]);
159
		if (ret[i] == OSS_STAT_NO_ERROR) {
160
			OssOutput *od = new OssOutput();
161
			if (!od->Initialize(empty, error)) {
162
				delete od;
163 164 165
				return NULL;
			}

166
			od->device = default_devices[i];
167
			return &od->base;
168
		}
169 170
	}

171
	for (int i = ARRAY_SIZE(default_devices); --i >= 0; ) {
172 173
		const char *dev = default_devices[i];
		switch(ret[i]) {
174 175 176
		case OSS_STAT_NO_ERROR:
			/* never reached */
			break;
177
		case OSS_STAT_DOESN_T_EXIST:
178 179
			FormatWarning(oss_output_domain,
				      "%s not found", dev);
180 181
			break;
		case OSS_STAT_NOT_CHAR_DEV:
182 183
			FormatWarning(oss_output_domain,
				      "%s is not a character device", dev);
184 185
			break;
		case OSS_STAT_NO_PERMS:
186 187
			FormatWarning(oss_output_domain,
				      "%s: permission denied", dev);
188
			break;
189
		case OSS_STAT_OTHER:
190 191
			FormatErrno(oss_output_domain, err[i],
				    "Error accessing %s", dev);
192 193
		}
	}
194

195 196
	error.Set(oss_output_domain,
		  "error trying to open default OSS device");
197
	return NULL;
198 199
}

200
static AudioOutput *
201
oss_output_init(const config_param &param, Error &error)
Avuton Olrich's avatar
Avuton Olrich committed
202
{
203
	const char *device = param.GetBlockValue("device");
Max Kellermann's avatar
Max Kellermann committed
204
	if (device != NULL) {
205
		OssOutput *od = new OssOutput();
206
		if (!od->Initialize(param, error)) {
207
			delete od;
208 209 210
			return NULL;
		}

Max Kellermann's avatar
Max Kellermann committed
211
		od->device = device;
212
		return &od->base;
213
	}
Max Kellermann's avatar
Max Kellermann committed
214

215
	return oss_open_default(error);
216 217
}

Max Kellermann's avatar
Max Kellermann committed
218
static void
219
oss_output_finish(AudioOutput *ao)
Avuton Olrich's avatar
Avuton Olrich committed
220
{
221
	OssOutput *od = (OssOutput *)ao;
222

223
	delete od;
224 225
}

226 227 228
#ifdef AFMT_S24_PACKED

static bool
229
oss_output_enable(AudioOutput *ao, gcc_unused Error &error)
230
{
231
	OssOutput *od = (OssOutput *)ao;
232

233
	od->pcm_export.Construct();
234 235 236 237
	return true;
}

static void
238
oss_output_disable(AudioOutput *ao)
239
{
240
	OssOutput *od = (OssOutput *)ao;
241

242
	od->pcm_export.Destruct();
243 244 245 246
}

#endif

Max Kellermann's avatar
Max Kellermann committed
247
static void
248
oss_close(OssOutput *od)
249
{
Avuton Olrich's avatar
Avuton Olrich committed
250
	if (od->fd >= 0)
251
		close(od->fd);
252 253 254
	od->fd = -1;
}

255
/**
256
 * A tri-state type for oss_try_ioctl().
257
 */
258 259 260 261 262 263 264 265 266
enum oss_setup_result {
	SUCCESS,
	ERROR,
	UNSUPPORTED,
};

/**
 * Invoke an ioctl on the OSS file descriptor.  On success, SUCCESS is
 * returned.  If the parameter is not supported, UNSUPPORTED is
267
 * returned.  Any other failure returns ERROR and allocates an #Error.
268 269 270
 */
static enum oss_setup_result
oss_try_ioctl_r(int fd, unsigned long request, int *value_r,
271
		const char *msg, Error &error)
Avuton Olrich's avatar
Avuton Olrich committed
272
{
273 274 275
	assert(fd >= 0);
	assert(value_r != NULL);
	assert(msg != NULL);
276
	assert(!error.IsDefined());
277

278 279 280 281 282 283 284
	int ret = ioctl(fd, request, value_r);
	if (ret >= 0)
		return SUCCESS;

	if (errno == EINVAL)
		return UNSUPPORTED;

285
	error.SetErrno(msg);
286 287 288 289 290 291
	return ERROR;
}

/**
 * Invoke an ioctl on the OSS file descriptor.  On success, SUCCESS is
 * returned.  If the parameter is not supported, UNSUPPORTED is
292
 * returned.  Any other failure returns ERROR and allocates an #Error.
293 294 295
 */
static enum oss_setup_result
oss_try_ioctl(int fd, unsigned long request, int value,
296
	      const char *msg, Error &error_r)
297 298 299 300 301 302 303 304 305
{
	return oss_try_ioctl_r(fd, request, &value, msg, error_r);
}

/**
 * Set up the channel number, and attempts to find alternatives if the
 * specified number is not supported.
 */
static bool
306
oss_setup_channels(int fd, AudioFormat &audio_format, Error &error)
307 308
{
	const char *const msg = "Failed to set channel count";
309
	int channels = audio_format.channels;
310
	enum oss_setup_result result =
311
		oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error);
312 313 314 315 316
	switch (result) {
	case SUCCESS:
		if (!audio_valid_channel_count(channels))
		    break;

317
		audio_format.channels = channels;
318 319 320
		return true;

	case ERROR:
321
		return false;
322 323 324

	case UNSUPPORTED:
		break;
325
	}
326

327
	for (unsigned i = 1; i < 2; ++i) {
328
		if (i == audio_format.channels)
329 330 331 332 333
			/* don't try that again */
			continue;

		channels = i;
		result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels,
334
					 msg, error);
335 336 337 338 339
		switch (result) {
		case SUCCESS:
			if (!audio_valid_channel_count(channels))
			    break;

340
			audio_format.channels = channels;
341 342 343 344 345 346 347 348 349 350
			return true;

		case ERROR:
			return false;

		case UNSUPPORTED:
			break;
		}
	}

351
	error.Set(oss_output_domain, msg);
352 353 354 355 356 357 358 359
	return false;
}

/**
 * Set up the sample rate, and attempts to find alternatives if the
 * specified sample rate is not supported.
 */
static bool
360
oss_setup_sample_rate(int fd, AudioFormat &audio_format,
361
		      Error &error)
362 363
{
	const char *const msg = "Failed to set sample rate";
364
	int sample_rate = audio_format.sample_rate;
365 366
	enum oss_setup_result result =
		oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
367
				msg, error);
368 369 370 371 372
	switch (result) {
	case SUCCESS:
		if (!audio_valid_sample_rate(sample_rate))
			break;

373
		audio_format.sample_rate = sample_rate;
374 375 376
		return true;

	case ERROR:
377
		return false;
378 379 380 381 382 383 384 385

	case UNSUPPORTED:
		break;
	}

	static const int sample_rates[] = { 48000, 44100, 0 };
	for (unsigned i = 0; sample_rates[i] != 0; ++i) {
		sample_rate = sample_rates[i];
386
		if (sample_rate == (int)audio_format.sample_rate)
387 388 389
			continue;

		result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate,
390
					 msg, error);
391 392 393 394
		switch (result) {
		case SUCCESS:
			if (!audio_valid_sample_rate(sample_rate))
				break;
395

396
			audio_format.sample_rate = sample_rate;
397 398 399 400 401 402 403 404
			return true;

		case ERROR:
			return false;

		case UNSUPPORTED:
			break;
		}
405
	}
406

407
	error.Set(oss_output_domain, msg);
408 409 410 411 412 413 414 415
	return false;
}

/**
 * Convert a MPD sample format to its OSS counterpart.  Returns
 * AFMT_QUERY if there is no direct counterpart.
 */
static int
416
sample_format_to_oss(SampleFormat format)
417 418
{
	switch (format) {
419 420 421
	case SampleFormat::UNDEFINED:
	case SampleFormat::FLOAT:
	case SampleFormat::DSD:
422 423
		return AFMT_QUERY;

424
	case SampleFormat::S8:
425
		return AFMT_S8;
426

427
	case SampleFormat::S16:
428 429
		return AFMT_S16_NE;

430
	case SampleFormat::S24_P32:
431 432 433 434 435 436
#ifdef AFMT_S24_NE
		return AFMT_S24_NE;
#else
		return AFMT_QUERY;
#endif

437
	case SampleFormat::S32:
438 439 440
#ifdef AFMT_S32_NE
		return AFMT_S32_NE;
#else
441
		return AFMT_QUERY;
442
#endif
443 444 445 446 447 448 449
	}

	return AFMT_QUERY;
}

/**
 * Convert an OSS sample format to its MPD counterpart.  Returns
450
 * SampleFormat::UNDEFINED if there is no direct counterpart.
451
 */
452
static SampleFormat
453 454 455 456
sample_format_from_oss(int format)
{
	switch (format) {
	case AFMT_S8:
457
		return SampleFormat::S8;
458 459

	case AFMT_S16_NE:
460
		return SampleFormat::S16;
461

462 463
#ifdef AFMT_S24_PACKED
	case AFMT_S24_PACKED:
464
		return SampleFormat::S24_P32;
465 466 467 468
#endif

#ifdef AFMT_S24_NE
	case AFMT_S24_NE:
469
		return SampleFormat::S24_P32;
470 471 472 473
#endif

#ifdef AFMT_S32_NE
	case AFMT_S32_NE:
474
		return SampleFormat::S32;
475 476
#endif

477
	default:
478
		return SampleFormat::UNDEFINED;
479
	}
480
}
481

482 483 484
/**
 * Probe one sample format.
 *
485
 * @return the selected sample format or SampleFormat::UNDEFINED on
486 487 488
 * error
 */
static enum oss_setup_result
489 490
oss_probe_sample_format(int fd, SampleFormat sample_format,
			SampleFormat *sample_format_r,
491
			int *oss_format_r,
492
#ifdef AFMT_S24_PACKED
493
			PcmExport &pcm_export,
494
#endif
495
			Error &error)
496 497 498 499 500 501 502 503
{
	int oss_format = sample_format_to_oss(sample_format);
	if (oss_format == AFMT_QUERY)
		return UNSUPPORTED;

	enum oss_setup_result result =
		oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
				&oss_format,
504
				"Failed to set sample format", error);
505 506

#ifdef AFMT_S24_PACKED
507
	if (result == UNSUPPORTED && sample_format == SampleFormat::S24_P32) {
508 509 510 511 512
		/* if the driver doesn't support padded 24 bit, try
		   packed 24 bit */
		oss_format = AFMT_S24_PACKED;
		result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE,
					 &oss_format,
513
					 "Failed to set sample format", error);
514 515 516
	}
#endif

517 518 519 520
	if (result != SUCCESS)
		return result;

	sample_format = sample_format_from_oss(oss_format);
521
	if (sample_format == SampleFormat::UNDEFINED)
522 523 524
		return UNSUPPORTED;

	*sample_format_r = sample_format;
525
	*oss_format_r = oss_format;
526 527

#ifdef AFMT_S24_PACKED
528
	pcm_export.Open(sample_format, 0, false, false,
529
			oss_format == AFMT_S24_PACKED,
530
			oss_format == AFMT_S24_PACKED &&
531
			!IsLittleEndian());
532 533 534 535 536
#endif

	return SUCCESS;
}

537 538 539 540 541
/**
 * Set up the sample format, and attempts to find alternatives if the
 * specified format is not supported.
 */
static bool
542
oss_setup_sample_format(int fd, AudioFormat &audio_format,
543
			int *oss_format_r,
544
#ifdef AFMT_S24_PACKED
545
			PcmExport &pcm_export,
546
#endif
547
			Error &error)
548
{
549
	SampleFormat mpd_format;
550
	enum oss_setup_result result =
551
		oss_probe_sample_format(fd, audio_format.format,
552
					&mpd_format, oss_format_r,
553
#ifdef AFMT_S24_PACKED
554
					pcm_export,
555
#endif
556
					error);
557 558
	switch (result) {
	case SUCCESS:
559
		audio_format.format = mpd_format;
560 561 562
		return true;

	case ERROR:
563
		return false;
564 565 566

	case UNSUPPORTED:
		break;
567
	}
568

569 570 571
	if (result != UNSUPPORTED)
		return result == SUCCESS;

572 573 574
	/* the requested sample format is not available - probe for
	   other formats supported by MPD */

575 576 577 578 579 580
	static const SampleFormat sample_formats[] = {
		SampleFormat::S24_P32,
		SampleFormat::S32,
		SampleFormat::S16,
		SampleFormat::S8,
		SampleFormat::UNDEFINED /* sentinel */
581 582
	};

583
	for (unsigned i = 0; sample_formats[i] != SampleFormat::UNDEFINED; ++i) {
584
		mpd_format = sample_formats[i];
585
		if (mpd_format == audio_format.format)
586 587 588
			/* don't try that again */
			continue;

589
		result = oss_probe_sample_format(fd, mpd_format,
590
						 &mpd_format, oss_format_r,
591
#ifdef AFMT_S24_PACKED
592
						 pcm_export,
593
#endif
594
						 error);
595 596
		switch (result) {
		case SUCCESS:
597
			audio_format.format = mpd_format;
598 599 600 601 602 603 604 605 606 607
			return true;

		case ERROR:
			return false;

		case UNSUPPORTED:
			break;
		}
	}

608
	error.Set(oss_output_domain, "Failed to set sample format");
609
	return false;
610
}
611

612 613 614
/**
 * Sets up the OSS device which was opened before.
 */
615
static bool
616
oss_setup(OssOutput *od, AudioFormat &audio_format,
617
	  Error &error)
618
{
619 620
	return oss_setup_channels(od->fd, audio_format, error) &&
		oss_setup_sample_rate(od->fd, audio_format, error) &&
621
		oss_setup_sample_format(od->fd, audio_format, &od->oss_format,
622
#ifdef AFMT_S24_PACKED
623
					od->pcm_export,
624
#endif
625
					error);
626 627 628 629 630 631
}

/**
 * Reopen the device with the saved audio_format, without any probing.
 */
static bool
632
oss_reopen(OssOutput *od, Error &error)
633 634
{
	assert(od->fd < 0);
635

636
	od->fd = open_cloexec(od->device, O_WRONLY, 0);
637
	if (od->fd < 0) {
638 639
		error.FormatErrno("Error opening OSS device \"%s\"",
				  od->device);
640 641 642
		return false;
	}

643 644 645 646
	enum oss_setup_result result;

	const char *const msg1 = "Failed to set channel count";
	result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS,
647
			       od->audio_format.channels, msg1, error);
648 649 650
	if (result != SUCCESS) {
		oss_close(od);
		if (result == UNSUPPORTED)
651
			error.Set(oss_output_domain, msg1);
652 653 654 655 656
		return false;
	}

	const char *const msg2 = "Failed to set sample rate";
	result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED,
657
			       od->audio_format.sample_rate, msg2, error);
658 659 660
	if (result != SUCCESS) {
		oss_close(od);
		if (result == UNSUPPORTED)
661
			error.Set(oss_output_domain, msg2);
662 663 664 665 666
		return false;
	}

	const char *const msg3 = "Failed to set sample format";
	result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE,
667
			       od->oss_format,
668
			       msg3, error);
669
	if (result != SUCCESS) {
670
		oss_close(od);
671
		if (result == UNSUPPORTED)
672
			error.Set(oss_output_domain, msg3);
673 674 675 676
		return false;
	}

	return true;
677 678
}

679
static bool
680
oss_output_open(AudioOutput *ao, AudioFormat &audio_format,
681
		Error &error)
682
{
683
	OssOutput *od = (OssOutput *)ao;
684

685 686
	od->fd = open_cloexec(od->device, O_WRONLY, 0);
	if (od->fd < 0) {
687 688
		error.FormatErrno("Error opening OSS device \"%s\"",
				  od->device);
689
		return false;
690
	}
691

692 693 694 695
	if (!oss_setup(od, audio_format, error)) {
		oss_close(od);
		return false;
	}
696

697
	od->audio_format = audio_format;
698
	return true;
699 700
}

Max Kellermann's avatar
Max Kellermann committed
701
static void
702
oss_output_close(AudioOutput *ao)
Avuton Olrich's avatar
Avuton Olrich committed
703
{
704
	OssOutput *od = (OssOutput *)ao;
705

706
	oss_close(od);
707 708
}

Max Kellermann's avatar
Max Kellermann committed
709
static void
710
oss_output_cancel(AudioOutput *ao)
Avuton Olrich's avatar
Avuton Olrich committed
711
{
712
	OssOutput *od = (OssOutput *)ao;
713

Avuton Olrich's avatar
Avuton Olrich committed
714
	if (od->fd >= 0) {
715
		ioctl(od->fd, SNDCTL_DSP_RESET, 0);
716
		oss_close(od);
717 718 719
	}
}

720
static size_t
721
oss_output_play(AudioOutput *ao, const void *chunk, size_t size,
722
		Error &error)
723
{
724
	OssOutput *od = (OssOutput *)ao;
725
	ssize_t ret;
726

727
	/* reopen the device since it was closed by dropBufferedAudio */
728
	if (od->fd < 0 && !oss_reopen(od, error))
729
		return 0;
730

731
#ifdef AFMT_S24_PACKED
732 733 734
	const auto e = od->pcm_export->Export({chunk, size});
	chunk = e.data;
	size = e.size;
735 736
#endif

737
	while (true) {
738
		ret = write(od->fd, chunk, size);
739 740
		if (ret > 0) {
#ifdef AFMT_S24_PACKED
741
			ret = od->pcm_export->CalcSourceSize(ret);
742 743 744
#endif
			return ret;
		}
745 746

		if (ret < 0 && errno != EINTR) {
747
			error.FormatErrno("Write error on %s", od->device);
748
			return 0;
749 750 751 752
		}
	}
}

753
const struct AudioOutputPlugin oss_output_plugin = {
754 755 756 757
	"oss",
	oss_output_test_default_device,
	oss_output_init,
	oss_output_finish,
758
#ifdef AFMT_S24_PACKED
759 760 761 762 763
	oss_output_enable,
	oss_output_disable,
#else
	nullptr,
	nullptr,
764
#endif
765 766 767 768 769 770 771 772 773 774
	oss_output_open,
	oss_output_close,
	nullptr,
	nullptr,
	oss_output_play,
	nullptr,
	oss_output_cancel,
	nullptr,

	&oss_mixer_plugin,
775
};