2009-01-26 15:06:44 +00:00
|
|
|
/*
|
|
|
|
* OSS audio output driver
|
|
|
|
*
|
2013-10-23 17:07:27 +00:00
|
|
|
* Original author: A'rpi
|
2013-06-07 12:29:59 +00:00
|
|
|
* Support for >2 output channels added 2001-11-25
|
|
|
|
* - Steve Davies <steve@daviesfam.org>
|
|
|
|
*
|
2015-04-13 07:36:54 +00:00
|
|
|
* This file is part of mpv.
|
|
|
|
*
|
|
|
|
* mpv is free software; you can redistribute it and/or modify
|
2009-01-26 15:06:44 +00:00
|
|
|
* 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.
|
|
|
|
*
|
2015-04-13 07:36:54 +00:00
|
|
|
* mpv is distributed in the hope that it will be useful,
|
2009-01-26 15:06:44 +00:00
|
|
|
* 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
|
2015-04-13 07:36:54 +00:00
|
|
|
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
2009-01-26 15:06:44 +00:00
|
|
|
*/
|
|
|
|
|
2001-06-02 23:25:43 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
2014-09-10 23:56:20 +00:00
|
|
|
#include <poll.h>
|
2002-03-19 19:16:01 +00:00
|
|
|
#include <errno.h>
|
|
|
|
#include <string.h>
|
2014-07-10 06:28:03 +00:00
|
|
|
#include <strings.h>
|
2001-06-02 23:25:43 +00:00
|
|
|
|
2004-12-07 02:24:15 +00:00
|
|
|
#include "config.h"
|
2013-12-17 01:02:25 +00:00
|
|
|
#include "options/options.h"
|
Do not call strerror()
...because everything is terrible.
strerror() is not documented as having to be thread-safe by POSIX and
C11. (Which is pretty much bullshit, because both mandate threads and
some form of thread-local storage - so there's no excuse why
implementation couldn't implement this in a thread-safe way. Especially
with C11 this is ridiculous, because there is no way to use threads and
convert error numbers to strings at the same time!)
Since we heavily use threads now, we should avoid unsafe functions like
strerror().
strerror_r() is in POSIX, but GNU/glibc deliberately fucks it up and
gives the function different semantics than the POSIX one. It's a bit of
work to convince this piece of shit to expose the POSIX standard
function, and not the messed up GNU one.
strerror_l() is also in POSIX, but only since the 2008 standard, and
thus is not widespread.
The solution is using avlibc (libavutil, by its official name), which
handles the unportable details for us, mostly. We avoid some pain.
2014-11-26 20:21:56 +00:00
|
|
|
#include "common/common.h"
|
2013-12-17 01:39:45 +00:00
|
|
|
#include "common/msg.h"
|
2014-09-15 20:25:01 +00:00
|
|
|
#include "osdep/timer.h"
|
2014-09-23 19:04:37 +00:00
|
|
|
#include "osdep/endian.h"
|
2001-06-02 23:25:43 +00:00
|
|
|
|
2004-12-27 17:30:15 +00:00
|
|
|
#include <sys/soundcard.h>
|
|
|
|
|
2012-11-09 00:06:43 +00:00
|
|
|
#include "audio/format.h"
|
2001-08-15 17:32:47 +00:00
|
|
|
|
2012-11-09 00:06:43 +00:00
|
|
|
#include "ao.h"
|
2014-03-07 14:24:32 +00:00
|
|
|
#include "internal.h"
|
2001-06-02 23:25:43 +00:00
|
|
|
|
2017-10-10 13:51:16 +00:00
|
|
|
#if !HAVE_GPL
|
|
|
|
#error GPL only
|
|
|
|
#endif
|
|
|
|
|
2014-09-15 18:37:07 +00:00
|
|
|
// Define to 0 if the device must be reopened to reset it (stop all playback,
|
|
|
|
// clear the buffer), and the device should be closed when unused.
|
|
|
|
// Define to 1 if SNDCTL_DSP_RESET should be used to reset without close.
|
2018-01-17 11:10:41 +00:00
|
|
|
#if defined(SNDCTL_DSP_RESET) && !defined(__NetBSD__)
|
|
|
|
#define KEEP_DEVICE 1
|
|
|
|
#else
|
|
|
|
#define KEEP_DEVICE 0
|
|
|
|
#endif
|
2014-09-15 18:37:07 +00:00
|
|
|
|
2017-06-22 08:30:11 +00:00
|
|
|
#define PATH_DEV_DSP "/dev/dsp"
|
|
|
|
#define PATH_DEV_MIXER "/dev/mixer"
|
|
|
|
|
2013-06-07 13:20:07 +00:00
|
|
|
struct priv {
|
|
|
|
int audio_fd;
|
2014-09-15 20:25:01 +00:00
|
|
|
int prepause_samples;
|
2013-06-07 13:20:07 +00:00
|
|
|
int oss_mixer_channel;
|
|
|
|
int audio_delay_method;
|
2013-06-16 17:15:32 +00:00
|
|
|
int buffersize;
|
|
|
|
int outburst;
|
2014-09-15 20:25:01 +00:00
|
|
|
bool device_failed;
|
|
|
|
double audio_end;
|
2013-07-21 21:52:40 +00:00
|
|
|
|
|
|
|
char *oss_mixer_device;
|
|
|
|
char *cfg_oss_mixer_channel;
|
2013-06-07 13:20:07 +00:00
|
|
|
};
|
|
|
|
|
2014-06-10 21:56:05 +00:00
|
|
|
static const char *const mixer_channels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
|
2013-07-21 21:52:40 +00:00
|
|
|
|
2013-11-30 07:46:36 +00:00
|
|
|
/* like alsa except for 6.1 and 7.1, from pcm/matrix_map.h */
|
|
|
|
static const struct mp_chmap oss_layouts[MP_NUM_CHANNELS + 1] = {
|
|
|
|
{0}, // empty
|
|
|
|
MP_CHMAP_INIT_MONO, // mono
|
|
|
|
MP_CHMAP2(FL, FR), // stereo
|
|
|
|
MP_CHMAP3(FL, FR, LFE), // 2.1
|
|
|
|
MP_CHMAP4(FL, FR, BL, BR), // 4.0
|
|
|
|
MP_CHMAP5(FL, FR, BL, BR, FC), // 5.0
|
|
|
|
MP_CHMAP6(FL, FR, BL, BR, FC, LFE), // 5.1
|
|
|
|
MP_CHMAP7(FL, FR, BL, BR, FC, LFE, BC), // 6.1
|
|
|
|
MP_CHMAP8(FL, FR, BL, BR, FC, LFE, SL, SR), // 7.1
|
|
|
|
};
|
|
|
|
|
2014-09-23 19:04:37 +00:00
|
|
|
#if !defined(AFMT_S16_NE) && defined(AFMT_S16_LE) && defined(AFMT_S16_BE)
|
|
|
|
#define AFMT_S16_NE MP_SELECT_LE_BE(AFMT_S16_LE, AFMT_S16_BE)
|
2004-12-27 17:30:15 +00:00
|
|
|
#endif
|
2014-09-23 19:04:37 +00:00
|
|
|
|
|
|
|
#if !defined(AFMT_S32_NE) && defined(AFMT_S32_LE) && defined(AFMT_S32_BE)
|
Do not call strerror()
...because everything is terrible.
strerror() is not documented as having to be thread-safe by POSIX and
C11. (Which is pretty much bullshit, because both mandate threads and
some form of thread-local storage - so there's no excuse why
implementation couldn't implement this in a thread-safe way. Especially
with C11 this is ridiculous, because there is no way to use threads and
convert error numbers to strings at the same time!)
Since we heavily use threads now, we should avoid unsafe functions like
strerror().
strerror_r() is in POSIX, but GNU/glibc deliberately fucks it up and
gives the function different semantics than the POSIX one. It's a bit of
work to convince this piece of shit to expose the POSIX standard
function, and not the messed up GNU one.
strerror_l() is also in POSIX, but only since the 2008 standard, and
thus is not widespread.
The solution is using avlibc (libavutil, by its official name), which
handles the unportable details for us, mostly. We avoid some pain.
2014-11-26 20:21:56 +00:00
|
|
|
#define AFMT_S32_NE AFMT_S32MP_SELECT_LE_BE(AFMT_S32_LE, AFMT_S32_BE)
|
2014-09-23 19:04:37 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
static const int format_table[][2] = {
|
|
|
|
{AFMT_U8, AF_FORMAT_U8},
|
|
|
|
{AFMT_S16_NE, AF_FORMAT_S16},
|
|
|
|
#ifdef AFMT_S32_NE
|
|
|
|
{AFMT_S32_NE, AF_FORMAT_S32},
|
2004-12-27 17:30:15 +00:00
|
|
|
#endif
|
|
|
|
#ifdef AFMT_FLOAT
|
2013-11-15 20:25:05 +00:00
|
|
|
{AFMT_FLOAT, AF_FORMAT_FLOAT},
|
2014-09-23 23:12:14 +00:00
|
|
|
#endif
|
|
|
|
#ifdef AFMT_MPEG
|
|
|
|
{AFMT_MPEG, AF_FORMAT_S_MP3},
|
2004-12-27 17:30:15 +00:00
|
|
|
#endif
|
2013-06-07 13:29:51 +00:00
|
|
|
{-1, -1}
|
|
|
|
};
|
|
|
|
|
audio: cleanup spdif format definitions
Before this commit, there was AF_FORMAT_AC3 (the original spdif format,
used for AC3 and DTS core), and AF_FORMAT_IEC61937 (used for AC3, DTS
and DTS-HD), which was handled as some sort of superset for
AF_FORMAT_AC3. There also was AF_FORMAT_MPEG2, which used
IEC61937-framing, but still was handled as something "separate".
Technically, all of them are pretty similar, but may use different
bitrates. Since digital passthrough pretends to be PCM (just with
special headers that wrap digital packets), this is easily detectable by
the higher samplerate or higher number of channels, so I don't know why
you'd need a separate "class" of sample formats (AF_FORMAT_AC3 vs.
AF_FORMAT_IEC61937) to distinguish them. Actually, this whole thing is
just a mess.
Simplify this by handling all these formats the same way.
AF_FORMAT_IS_IEC61937() now returns 1 for all spdif formats (even MP3).
All AOs just accept all spdif formats now - whether that works or not is
not really clear (seems inconsistent due to earlier attempts to make
DTS-HD work). But on the other hand, enabling spdif requires manual user
interaction, so it doesn't matter much if initialization fails in
slightly less graceful ways if it can't work at all.
At a later point, we will support passthrough with ao_pulse. It seems
the PulseAudio API wants to know the codec type (or maybe not - feeding
it DTS while telling it it's AC3 works), add separate formats for each
codecs. While this reminds of the earlier chaos, it's stricter, and most
code just uses AF_FORMAT_IS_IEC61937().
Also, modify AF_FORMAT_TYPE_MASK (renamed from AF_FORMAT_POINT_MASK) to
include special formats, so that it always describes the fundamental
sample format type. This also ensures valid AF formats are never 0 (this
was probably broken in one of the earlier commits from today).
2014-09-23 20:44:54 +00:00
|
|
|
#ifndef AFMT_AC3
|
|
|
|
#define AFMT_AC3 -1
|
|
|
|
#endif
|
|
|
|
|
2013-06-07 13:29:51 +00:00
|
|
|
static int format2oss(int format)
|
|
|
|
{
|
|
|
|
for (int n = 0; format_table[n][0] != -1; n++) {
|
|
|
|
if (format_table[n][1] == format)
|
|
|
|
return format_table[n][0];
|
2004-12-27 17:30:15 +00:00
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int oss2format(int format)
|
|
|
|
{
|
2013-06-07 13:29:51 +00:00
|
|
|
for (int n = 0; format_table[n][0] != -1; n++) {
|
|
|
|
if (format_table[n][0] == format)
|
|
|
|
return format_table[n][1];
|
2004-12-27 17:30:15 +00:00
|
|
|
}
|
2014-09-23 23:12:14 +00:00
|
|
|
return 0;
|
2004-12-27 17:30:15 +00:00
|
|
|
}
|
|
|
|
|
2001-06-21 23:07:15 +00:00
|
|
|
|
2009-06-23 04:31:13 +00:00
|
|
|
#ifdef SNDCTL_DSP_GETPLAYVOL
|
2013-06-11 10:21:57 +00:00
|
|
|
static int volume_oss4(struct ao *ao, ao_control_vol_t *vol, int cmd)
|
2013-06-07 12:29:59 +00:00
|
|
|
{
|
2013-06-11 10:21:57 +00:00
|
|
|
struct priv *p = ao->priv;
|
2009-06-23 04:31:13 +00:00
|
|
|
int v;
|
|
|
|
|
2013-06-07 13:20:07 +00:00
|
|
|
if (p->audio_fd < 0)
|
2009-06-23 04:31:13 +00:00
|
|
|
return CONTROL_ERROR;
|
|
|
|
|
|
|
|
if (cmd == AOCONTROL_GET_VOLUME) {
|
2013-06-07 13:20:07 +00:00
|
|
|
if (ioctl(p->audio_fd, SNDCTL_DSP_GETPLAYVOL, &v) == -1)
|
2009-06-23 04:31:13 +00:00
|
|
|
return CONTROL_ERROR;
|
|
|
|
vol->right = (v & 0xff00) >> 8;
|
|
|
|
vol->left = v & 0x00ff;
|
|
|
|
return CONTROL_OK;
|
|
|
|
} else if (cmd == AOCONTROL_SET_VOLUME) {
|
|
|
|
v = ((int) vol->right << 8) | (int) vol->left;
|
2013-06-07 13:20:07 +00:00
|
|
|
if (ioctl(p->audio_fd, SNDCTL_DSP_SETPLAYVOL, &v) == -1)
|
2009-06-23 04:31:13 +00:00
|
|
|
return CONTROL_ERROR;
|
|
|
|
return CONTROL_OK;
|
|
|
|
} else
|
|
|
|
return CONTROL_UNKNOWN;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2001-06-02 23:25:43 +00:00
|
|
|
// to set/get/query special features/parameters
|
2013-06-07 13:05:20 +00:00
|
|
|
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
2013-06-07 12:29:59 +00:00
|
|
|
{
|
2013-06-07 13:20:07 +00:00
|
|
|
struct priv *p = ao->priv;
|
2013-06-07 12:29:59 +00:00
|
|
|
switch (cmd) {
|
|
|
|
case AOCONTROL_GET_VOLUME:
|
2014-09-06 00:30:57 +00:00
|
|
|
case AOCONTROL_SET_VOLUME: {
|
2013-06-07 12:29:59 +00:00
|
|
|
ao_control_vol_t *vol = (ao_control_vol_t *)arg;
|
|
|
|
int fd, v, devs;
|
2001-08-15 11:50:55 +00:00
|
|
|
|
2009-06-23 04:31:13 +00:00
|
|
|
#ifdef SNDCTL_DSP_GETPLAYVOL
|
|
|
|
// Try OSS4 first
|
2013-06-11 10:21:57 +00:00
|
|
|
if (volume_oss4(ao, vol, cmd) == CONTROL_OK)
|
2009-06-23 04:31:13 +00:00
|
|
|
return CONTROL_OK;
|
|
|
|
#endif
|
|
|
|
|
2015-06-26 21:06:37 +00:00
|
|
|
if (!af_fmt_is_pcm(ao->format))
|
2013-06-07 12:29:59 +00:00
|
|
|
return CONTROL_TRUE;
|
|
|
|
|
2013-06-07 13:20:07 +00:00
|
|
|
if ((fd = open(p->oss_mixer_device, O_RDONLY)) != -1) {
|
2013-06-07 12:29:59 +00:00
|
|
|
ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devs);
|
2013-06-07 13:20:07 +00:00
|
|
|
if (devs & (1 << p->oss_mixer_channel)) {
|
2013-06-07 12:29:59 +00:00
|
|
|
if (cmd == AOCONTROL_GET_VOLUME) {
|
2013-06-07 13:20:07 +00:00
|
|
|
ioctl(fd, MIXER_READ(p->oss_mixer_channel), &v);
|
2013-06-07 12:29:59 +00:00
|
|
|
vol->right = (v & 0xFF00) >> 8;
|
|
|
|
vol->left = v & 0x00FF;
|
|
|
|
} else {
|
|
|
|
v = ((int)vol->right << 8) | (int)vol->left;
|
2013-06-07 13:20:07 +00:00
|
|
|
ioctl(fd, MIXER_WRITE(p->oss_mixer_channel), &v);
|
2013-06-07 12:29:59 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
close(fd);
|
|
|
|
return CONTROL_ERROR;
|
|
|
|
}
|
|
|
|
close(fd);
|
|
|
|
return CONTROL_OK;
|
|
|
|
}
|
|
|
|
return CONTROL_ERROR;
|
2001-06-02 23:25:43 +00:00
|
|
|
}
|
2014-09-06 00:30:57 +00:00
|
|
|
#ifdef SNDCTL_DSP_GETPLAYVOL
|
|
|
|
case AOCONTROL_HAS_SOFT_VOLUME:
|
|
|
|
return CONTROL_TRUE;
|
|
|
|
#endif
|
|
|
|
}
|
2001-06-02 23:25:43 +00:00
|
|
|
return CONTROL_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
2014-09-10 23:56:20 +00:00
|
|
|
// 1: ok, 0: not writable, -1: error
|
|
|
|
static int device_writable(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
struct pollfd fd = {.fd = p->audio_fd, .events = POLLOUT};
|
|
|
|
return poll(&fd, 1, 0);
|
|
|
|
}
|
|
|
|
|
2014-09-15 19:01:27 +00:00
|
|
|
static void close_device(struct ao *ao)
|
2014-09-11 00:05:12 +00:00
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
2014-09-15 20:25:01 +00:00
|
|
|
p->device_failed = false;
|
2014-09-11 00:05:12 +00:00
|
|
|
if (p->audio_fd == -1)
|
|
|
|
return;
|
2014-09-15 18:37:07 +00:00
|
|
|
#if defined(SNDCTL_DSP_RESET)
|
2014-09-11 00:05:12 +00:00
|
|
|
ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
|
|
|
|
#endif
|
|
|
|
close(p->audio_fd);
|
|
|
|
p->audio_fd = -1;
|
|
|
|
}
|
|
|
|
|
2014-09-15 19:01:27 +00:00
|
|
|
// close audio device
|
|
|
|
static void uninit(struct ao *ao)
|
|
|
|
{
|
|
|
|
close_device(ao);
|
|
|
|
}
|
|
|
|
|
2014-09-23 23:12:14 +00:00
|
|
|
static bool try_format(struct ao *ao, int *format)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
|
|
|
int oss_format = format2oss(*format);
|
2015-06-26 21:06:37 +00:00
|
|
|
if (oss_format == -1 && af_fmt_is_spdif(*format))
|
2014-09-23 23:12:14 +00:00
|
|
|
oss_format = AFMT_AC3;
|
|
|
|
|
|
|
|
if (oss_format == -1) {
|
|
|
|
MP_VERBOSE(ao, "Unknown/not supported internal format: %s\n",
|
|
|
|
af_fmt_to_str(*format));
|
|
|
|
*format = 0;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int actual_format = oss_format;
|
|
|
|
if (ioctl(p->audio_fd, SNDCTL_DSP_SETFMT, &actual_format) < 0)
|
|
|
|
actual_format = -1;
|
|
|
|
|
|
|
|
if (actual_format == oss_format)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
MP_WARN(ao, "Can't set audio device to %s output.\n", af_fmt_to_str(*format));
|
|
|
|
*format = oss2format(actual_format);
|
|
|
|
if (actual_format != -1 && !*format)
|
|
|
|
MP_ERR(ao, "Unknown/Unsupported OSS format: 0x%x.\n", actual_format);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-09-15 19:01:27 +00:00
|
|
|
static int reopen_device(struct ao *ao, bool allow_format_changes)
|
2013-06-07 12:29:59 +00:00
|
|
|
{
|
2013-07-21 21:52:40 +00:00
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
2014-09-15 19:01:27 +00:00
|
|
|
int samplerate = ao->samplerate;
|
|
|
|
int format = ao->format;
|
|
|
|
struct mp_chmap channels = ao->channels;
|
2001-06-21 23:07:15 +00:00
|
|
|
|
2017-02-08 06:24:27 +00:00
|
|
|
const char *device = PATH_DEV_DSP;
|
|
|
|
if (ao->device)
|
|
|
|
device = ao->device;
|
|
|
|
|
|
|
|
MP_VERBOSE(ao, "using '%s' dsp device\n", device);
|
2002-04-29 20:42:15 +00:00
|
|
|
#ifdef __linux__
|
2017-02-08 06:24:27 +00:00
|
|
|
p->audio_fd = open(device, O_WRONLY | O_NONBLOCK);
|
2002-04-29 20:42:15 +00:00
|
|
|
#else
|
2017-02-08 06:24:27 +00:00
|
|
|
p->audio_fd = open(device, O_WRONLY);
|
2002-04-29 20:42:15 +00:00
|
|
|
#endif
|
2013-06-07 13:20:07 +00:00
|
|
|
if (p->audio_fd < 0) {
|
Do not call strerror()
...because everything is terrible.
strerror() is not documented as having to be thread-safe by POSIX and
C11. (Which is pretty much bullshit, because both mandate threads and
some form of thread-local storage - so there's no excuse why
implementation couldn't implement this in a thread-safe way. Especially
with C11 this is ridiculous, because there is no way to use threads and
convert error numbers to strings at the same time!)
Since we heavily use threads now, we should avoid unsafe functions like
strerror().
strerror_r() is in POSIX, but GNU/glibc deliberately fucks it up and
gives the function different semantics than the POSIX one. It's a bit of
work to convince this piece of shit to expose the POSIX standard
function, and not the messed up GNU one.
strerror_l() is also in POSIX, but only since the 2008 standard, and
thus is not widespread.
The solution is using avlibc (libavutil, by its official name), which
handles the unportable details for us, mostly. We avoid some pain.
2014-11-26 20:21:56 +00:00
|
|
|
MP_ERR(ao, "Can't open audio device %s: %s\n",
|
2017-02-08 06:24:27 +00:00
|
|
|
device, mp_strerror(errno));
|
2014-09-11 00:05:12 +00:00
|
|
|
goto fail;
|
2013-06-07 12:29:59 +00:00
|
|
|
}
|
2001-06-02 23:25:43 +00:00
|
|
|
|
2002-04-29 20:42:15 +00:00
|
|
|
#ifdef __linux__
|
2013-06-07 12:29:59 +00:00
|
|
|
/* Remove the non-blocking flag */
|
2013-06-07 13:20:07 +00:00
|
|
|
if (fcntl(p->audio_fd, F_SETFL, 0) < 0) {
|
Do not call strerror()
...because everything is terrible.
strerror() is not documented as having to be thread-safe by POSIX and
C11. (Which is pretty much bullshit, because both mandate threads and
some form of thread-local storage - so there's no excuse why
implementation couldn't implement this in a thread-safe way. Especially
with C11 this is ridiculous, because there is no way to use threads and
convert error numbers to strings at the same time!)
Since we heavily use threads now, we should avoid unsafe functions like
strerror().
strerror_r() is in POSIX, but GNU/glibc deliberately fucks it up and
gives the function different semantics than the POSIX one. It's a bit of
work to convince this piece of shit to expose the POSIX standard
function, and not the messed up GNU one.
strerror_l() is also in POSIX, but only since the 2008 standard, and
thus is not widespread.
The solution is using avlibc (libavutil, by its official name), which
handles the unportable details for us, mostly. We avoid some pain.
2014-11-26 20:21:56 +00:00
|
|
|
MP_ERR(ao, "Can't make file descriptor blocking: %s\n",
|
|
|
|
mp_strerror(errno));
|
2014-09-11 00:05:12 +00:00
|
|
|
goto fail;
|
2013-06-07 12:29:59 +00:00
|
|
|
}
|
2002-04-29 20:42:15 +00:00
|
|
|
#endif
|
2002-11-28 16:14:08 +00:00
|
|
|
|
|
|
|
#if defined(FD_CLOEXEC) && defined(F_SETFD)
|
2013-06-07 13:20:07 +00:00
|
|
|
fcntl(p->audio_fd, F_SETFD, FD_CLOEXEC);
|
2002-11-28 16:14:08 +00:00
|
|
|
#endif
|
2009-07-06 23:26:13 +00:00
|
|
|
|
2015-06-26 21:06:37 +00:00
|
|
|
if (af_fmt_is_spdif(format)) {
|
2014-11-21 09:05:46 +00:00
|
|
|
if (ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &samplerate) == -1)
|
|
|
|
goto fail;
|
2014-09-23 23:12:14 +00:00
|
|
|
// Probably could be fixed by setting number of channels; needs testing.
|
|
|
|
if (channels.num != 2) {
|
|
|
|
MP_ERR(ao, "Format %s not implemented.\n", af_fmt_to_str(format));
|
|
|
|
goto fail;
|
|
|
|
}
|
2013-06-07 12:29:59 +00:00
|
|
|
}
|
2002-04-22 22:33:06 +00:00
|
|
|
|
2018-01-23 23:02:13 +00:00
|
|
|
int try_formats[AF_FORMAT_COUNT + 1];
|
2015-09-10 21:39:46 +00:00
|
|
|
af_get_best_sample_formats(format, try_formats);
|
2014-09-23 23:12:14 +00:00
|
|
|
for (int n = 0; try_formats[n]; n++) {
|
|
|
|
format = try_formats[n];
|
|
|
|
if (try_format(ao, &format))
|
|
|
|
break;
|
2001-11-28 12:46:23 +00:00
|
|
|
}
|
2013-06-07 12:29:59 +00:00
|
|
|
|
2014-09-23 23:12:14 +00:00
|
|
|
if (!format) {
|
|
|
|
MP_ERR(ao, "Can't set sample format.\n");
|
2014-09-11 00:05:12 +00:00
|
|
|
goto fail;
|
2013-08-23 21:30:09 +00:00
|
|
|
}
|
2013-06-07 12:29:59 +00:00
|
|
|
|
2014-09-15 19:01:27 +00:00
|
|
|
MP_VERBOSE(ao, "sample format: %s\n", af_fmt_to_str(format));
|
2013-06-07 12:29:59 +00:00
|
|
|
|
2015-06-26 21:06:37 +00:00
|
|
|
if (!af_fmt_is_spdif(format)) {
|
2013-06-07 12:29:59 +00:00
|
|
|
struct mp_chmap_sel sel = {0};
|
2013-11-30 07:46:36 +00:00
|
|
|
for (int n = 0; n < MP_NUM_CHANNELS + 1; n++)
|
|
|
|
mp_chmap_sel_add_map(&sel, &oss_layouts[n]);
|
2014-09-15 19:01:27 +00:00
|
|
|
if (!ao_chmap_sel_adjust(ao, &sel, &channels))
|
2014-09-11 00:05:12 +00:00
|
|
|
goto fail;
|
2014-09-15 19:01:27 +00:00
|
|
|
int reqchannels = channels.num;
|
2013-06-07 12:29:59 +00:00
|
|
|
// We only use SNDCTL_DSP_CHANNELS for >2 channels, in case some drivers don't have it
|
|
|
|
if (reqchannels > 2) {
|
|
|
|
int nchannels = reqchannels;
|
2013-06-07 13:20:07 +00:00
|
|
|
if (ioctl(p->audio_fd, SNDCTL_DSP_CHANNELS, &nchannels) == -1 ||
|
2013-06-07 12:29:59 +00:00
|
|
|
nchannels != reqchannels)
|
|
|
|
{
|
2013-08-23 21:30:09 +00:00
|
|
|
MP_ERR(ao, "Failed to set audio device to %d channels.\n",
|
|
|
|
reqchannels);
|
2014-09-11 00:05:12 +00:00
|
|
|
goto fail;
|
2013-06-07 12:29:59 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
int c = reqchannels - 1;
|
2013-06-07 13:20:07 +00:00
|
|
|
if (ioctl(p->audio_fd, SNDCTL_DSP_STEREO, &c) == -1) {
|
2013-08-23 21:30:09 +00:00
|
|
|
MP_ERR(ao, "Failed to set audio device to %d channels.\n",
|
|
|
|
reqchannels);
|
2014-09-11 00:05:12 +00:00
|
|
|
goto fail;
|
2013-06-07 12:29:59 +00:00
|
|
|
}
|
2014-09-15 19:01:27 +00:00
|
|
|
if (!ao_chmap_sel_get_def(ao, &sel, &channels, c + 1))
|
2014-09-11 00:05:12 +00:00
|
|
|
goto fail;
|
2013-06-07 12:29:59 +00:00
|
|
|
}
|
2013-08-23 21:30:09 +00:00
|
|
|
MP_VERBOSE(ao, "using %d channels (requested: %d)\n",
|
2014-09-15 19:01:27 +00:00
|
|
|
channels.num, reqchannels);
|
2013-06-07 12:29:59 +00:00
|
|
|
// set rate
|
2014-11-21 09:05:46 +00:00
|
|
|
if (ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &samplerate) == -1)
|
|
|
|
goto fail;
|
2014-09-15 19:01:27 +00:00
|
|
|
MP_VERBOSE(ao, "using %d Hz samplerate\n", samplerate);
|
2013-06-07 12:29:59 +00:00
|
|
|
}
|
|
|
|
|
2014-09-15 19:22:02 +00:00
|
|
|
audio_buf_info zz = {0};
|
|
|
|
if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &zz) == -1) {
|
2013-06-07 12:29:59 +00:00
|
|
|
int r = 0;
|
2013-08-23 21:30:09 +00:00
|
|
|
MP_WARN(ao, "driver doesn't support SNDCTL_DSP_GETOSPACE\n");
|
2013-06-07 13:20:07 +00:00
|
|
|
if (ioctl(p->audio_fd, SNDCTL_DSP_GETBLKSIZE, &r) == -1)
|
2013-08-23 21:30:09 +00:00
|
|
|
MP_VERBOSE(ao, "%d bytes/frag (config.h)\n", p->outburst);
|
2013-06-07 12:29:59 +00:00
|
|
|
else {
|
2013-06-16 17:15:32 +00:00
|
|
|
p->outburst = r;
|
2013-08-23 21:30:09 +00:00
|
|
|
MP_VERBOSE(ao, "%d bytes/frag (GETBLKSIZE)\n", p->outburst);
|
2013-06-07 12:29:59 +00:00
|
|
|
}
|
|
|
|
} else {
|
2013-08-23 21:30:09 +00:00
|
|
|
MP_VERBOSE(ao, "frags: %3d/%d (%d bytes/frag) free: %6d\n",
|
2014-09-15 19:22:02 +00:00
|
|
|
zz.fragments, zz.fragstotal, zz.fragsize, zz.bytes);
|
|
|
|
p->buffersize = zz.bytes;
|
|
|
|
p->outburst = zz.fragsize;
|
2001-11-28 12:46:23 +00:00
|
|
|
}
|
2013-06-07 12:29:59 +00:00
|
|
|
|
2014-09-15 19:01:27 +00:00
|
|
|
if (allow_format_changes) {
|
|
|
|
ao->format = format;
|
|
|
|
ao->samplerate = samplerate;
|
|
|
|
ao->channels = channels;
|
|
|
|
} else {
|
|
|
|
if (format != ao->format || samplerate != ao->samplerate ||
|
|
|
|
!mp_chmap_equals(&channels, &ao->channels))
|
|
|
|
{
|
|
|
|
MP_ERR(ao, "Could not reselect previous audio format.\n");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-21 17:45:59 +00:00
|
|
|
int sstride = channels.num * af_fmt_to_bytes(format);
|
|
|
|
p->outburst -= p->outburst % sstride; // round down
|
|
|
|
ao->period_size = p->outburst / sstride;
|
2014-09-15 19:01:27 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
close_device(ao);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// open & setup audio device
|
|
|
|
// return: 0=success -1=fail
|
|
|
|
static int init(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
|
|
|
const char *mchan = NULL;
|
|
|
|
if (p->cfg_oss_mixer_channel && p->cfg_oss_mixer_channel[0])
|
|
|
|
mchan = p->cfg_oss_mixer_channel;
|
|
|
|
|
|
|
|
if (mchan) {
|
|
|
|
int fd, devs, i;
|
|
|
|
|
|
|
|
if ((fd = open(p->oss_mixer_device, O_RDONLY)) == -1) {
|
|
|
|
MP_ERR(ao, "Can't open mixer device %s: %s\n",
|
Do not call strerror()
...because everything is terrible.
strerror() is not documented as having to be thread-safe by POSIX and
C11. (Which is pretty much bullshit, because both mandate threads and
some form of thread-local storage - so there's no excuse why
implementation couldn't implement this in a thread-safe way. Especially
with C11 this is ridiculous, because there is no way to use threads and
convert error numbers to strings at the same time!)
Since we heavily use threads now, we should avoid unsafe functions like
strerror().
strerror_r() is in POSIX, but GNU/glibc deliberately fucks it up and
gives the function different semantics than the POSIX one. It's a bit of
work to convince this piece of shit to expose the POSIX standard
function, and not the messed up GNU one.
strerror_l() is also in POSIX, but only since the 2008 standard, and
thus is not widespread.
The solution is using avlibc (libavutil, by its official name), which
handles the unportable details for us, mostly. We avoid some pain.
2014-11-26 20:21:56 +00:00
|
|
|
p->oss_mixer_device, mp_strerror(errno));
|
2014-09-15 19:01:27 +00:00
|
|
|
} else {
|
|
|
|
ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devs);
|
|
|
|
close(fd);
|
|
|
|
|
|
|
|
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
|
|
|
|
if (!strcasecmp(mixer_channels[i], mchan)) {
|
|
|
|
if (!(devs & (1 << i))) {
|
|
|
|
MP_ERR(ao, "Audio card mixer does not have "
|
|
|
|
"channel '%s', using default.\n", mchan);
|
|
|
|
i = SOUND_MIXER_NRDEVICES + 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
p->oss_mixer_channel = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i == SOUND_MIXER_NRDEVICES) {
|
|
|
|
MP_ERR(ao, "Audio card mixer does not have "
|
|
|
|
"channel '%s', using default.\n", mchan);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
p->oss_mixer_channel = SOUND_MIXER_PCM;
|
|
|
|
}
|
|
|
|
|
|
|
|
MP_VERBOSE(ao, "using '%s' mixer device\n", p->oss_mixer_device);
|
2017-02-08 08:31:15 +00:00
|
|
|
MP_VERBOSE(ao, "using '%s' mixer channel\n", mixer_channels[p->oss_mixer_channel]);
|
2014-09-15 19:01:27 +00:00
|
|
|
|
|
|
|
ao->format = af_fmt_from_planar(ao->format);
|
|
|
|
|
|
|
|
if (reopen_device(ao, true) < 0)
|
|
|
|
goto fail;
|
|
|
|
|
2013-06-16 17:15:32 +00:00
|
|
|
if (p->buffersize == -1) {
|
2013-06-07 12:29:59 +00:00
|
|
|
// Measuring buffer size:
|
2014-09-10 23:56:20 +00:00
|
|
|
void *data = malloc(p->outburst);
|
|
|
|
if (!data) {
|
|
|
|
MP_ERR(ao, "Out of memory, or broken outburst size.\n");
|
2014-09-11 00:05:12 +00:00
|
|
|
goto fail;
|
2014-09-10 23:56:20 +00:00
|
|
|
}
|
2013-06-16 17:15:32 +00:00
|
|
|
p->buffersize = 0;
|
|
|
|
memset(data, 0, p->outburst);
|
2014-09-10 23:56:20 +00:00
|
|
|
while (p->buffersize < 0x40000 && device_writable(ao) > 0) {
|
2016-06-07 11:39:43 +00:00
|
|
|
(void)write(p->audio_fd, data, p->outburst);
|
2013-06-16 17:15:32 +00:00
|
|
|
p->buffersize += p->outburst;
|
2013-06-07 12:29:59 +00:00
|
|
|
}
|
|
|
|
free(data);
|
2013-06-16 17:15:32 +00:00
|
|
|
if (p->buffersize == 0) {
|
2014-09-10 23:56:20 +00:00
|
|
|
MP_ERR(ao, "Your OSS audio driver DOES NOT support poll().\n");
|
2014-09-11 00:05:12 +00:00
|
|
|
goto fail;
|
2013-06-07 12:29:59 +00:00
|
|
|
}
|
2001-06-02 23:25:43 +00:00
|
|
|
}
|
2013-06-07 12:29:59 +00:00
|
|
|
|
2013-06-07 13:05:20 +00:00
|
|
|
return 0;
|
2001-06-02 23:25:43 +00:00
|
|
|
|
2014-09-11 00:05:12 +00:00
|
|
|
fail:
|
|
|
|
uninit(ao);
|
|
|
|
return -1;
|
2013-06-07 13:20:07 +00:00
|
|
|
}
|
|
|
|
|
2014-03-08 23:49:39 +00:00
|
|
|
static void drain(struct ao *ao)
|
|
|
|
{
|
|
|
|
#ifdef SNDCTL_DSP_SYNC
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
// to get the buffer played
|
|
|
|
if (p->audio_fd != -1)
|
|
|
|
ioctl(p->audio_fd, SNDCTL_DSP_SYNC, NULL);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2001-06-02 23:25:43 +00:00
|
|
|
// stop playing and empty buffers (for seeking/pause)
|
2013-06-07 13:05:20 +00:00
|
|
|
static void reset(struct ao *ao)
|
2013-06-07 12:29:59 +00:00
|
|
|
{
|
2014-09-15 18:37:07 +00:00
|
|
|
#if KEEP_DEVICE
|
2014-09-15 19:01:27 +00:00
|
|
|
struct priv *p = ao->priv;
|
2013-11-04 20:54:11 +00:00
|
|
|
ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
|
|
|
|
#else
|
2013-06-07 13:20:07 +00:00
|
|
|
close_device(ao);
|
2014-09-15 18:40:30 +00:00
|
|
|
#endif
|
2001-06-05 18:40:44 +00:00
|
|
|
}
|
|
|
|
|
2014-09-15 18:22:12 +00:00
|
|
|
// plays 'len' samples of 'data'
|
2001-06-02 23:25:43 +00:00
|
|
|
// it should round it down to outburst*n
|
2014-09-15 18:22:12 +00:00
|
|
|
// return: number of samples played
|
2013-11-10 22:24:21 +00:00
|
|
|
static int play(struct ao *ao, void **data, int samples, int flags)
|
2013-06-07 12:29:59 +00:00
|
|
|
{
|
2013-06-07 13:20:07 +00:00
|
|
|
struct priv *p = ao->priv;
|
2013-11-10 22:24:21 +00:00
|
|
|
int len = samples * ao->sstride;
|
2013-06-07 12:29:59 +00:00
|
|
|
if (len == 0)
|
2006-06-28 19:22:27 +00:00
|
|
|
return len;
|
2014-09-15 20:25:01 +00:00
|
|
|
|
|
|
|
if (p->audio_fd < 0 && !p->device_failed && reopen_device(ao, false) < 0)
|
|
|
|
MP_ERR(ao, "Fatal error: *** CANNOT RE-OPEN / RESET AUDIO DEVICE ***\n");
|
|
|
|
if (p->audio_fd < 0) {
|
|
|
|
// Let playback continue normally, even with a closed device.
|
|
|
|
p->device_failed = true;
|
|
|
|
double now = mp_time_sec();
|
|
|
|
if (p->audio_end < now)
|
|
|
|
p->audio_end = now;
|
|
|
|
p->audio_end += samples / (double)ao->samplerate;
|
|
|
|
return samples;
|
|
|
|
}
|
|
|
|
|
2013-06-16 17:15:32 +00:00
|
|
|
if (len > p->outburst || !(flags & AOPLAY_FINAL_CHUNK)) {
|
|
|
|
len /= p->outburst;
|
|
|
|
len *= p->outburst;
|
2006-06-28 19:22:27 +00:00
|
|
|
}
|
2013-11-10 22:24:21 +00:00
|
|
|
len = write(p->audio_fd, data[0], len);
|
|
|
|
return len / ao->sstride;
|
2001-06-02 23:25:43 +00:00
|
|
|
}
|
|
|
|
|
2001-11-24 05:21:22 +00:00
|
|
|
// return: delay in seconds between first and last sample in buffer
|
2014-11-09 10:45:04 +00:00
|
|
|
static double get_delay(struct ao *ao)
|
2013-06-07 12:29:59 +00:00
|
|
|
{
|
2013-06-07 13:20:07 +00:00
|
|
|
struct priv *p = ao->priv;
|
2014-09-15 20:25:01 +00:00
|
|
|
if (p->audio_fd < 0) {
|
|
|
|
double rest = p->audio_end - mp_time_sec();
|
|
|
|
if (rest > 0)
|
|
|
|
return rest;
|
|
|
|
return 0;
|
|
|
|
}
|
2013-06-07 12:29:59 +00:00
|
|
|
/* Calculate how many bytes/second is sent out */
|
2013-06-07 13:20:07 +00:00
|
|
|
if (p->audio_delay_method == 2) {
|
2002-04-27 22:42:27 +00:00
|
|
|
#ifdef SNDCTL_DSP_GETODELAY
|
2013-06-07 12:29:59 +00:00
|
|
|
int r = 0;
|
2013-06-07 13:20:07 +00:00
|
|
|
if (ioctl(p->audio_fd, SNDCTL_DSP_GETODELAY, &r) != -1)
|
2014-11-09 10:45:04 +00:00
|
|
|
return r / (double)ao->bps;
|
2002-04-27 22:42:27 +00:00
|
|
|
#endif
|
2013-06-07 13:20:07 +00:00
|
|
|
p->audio_delay_method = 1; // fallback if not supported
|
2013-06-07 12:29:59 +00:00
|
|
|
}
|
2013-06-07 13:20:07 +00:00
|
|
|
if (p->audio_delay_method == 1) {
|
2014-09-15 19:22:02 +00:00
|
|
|
audio_buf_info zz = {0};
|
|
|
|
if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &zz) != -1) {
|
2014-11-09 10:45:04 +00:00
|
|
|
return (p->buffersize - zz.bytes) / (double)ao->bps;
|
2013-06-07 12:29:59 +00:00
|
|
|
}
|
2013-06-07 13:20:07 +00:00
|
|
|
p->audio_delay_method = 0; // fallback if not supported
|
2013-06-07 12:29:59 +00:00
|
|
|
}
|
2014-11-09 10:45:04 +00:00
|
|
|
return p->buffersize / (double)ao->bps;
|
2001-06-02 23:25:43 +00:00
|
|
|
}
|
2013-06-07 13:05:20 +00:00
|
|
|
|
2014-09-16 22:14:21 +00:00
|
|
|
|
|
|
|
// return: how many samples can be played without blocking
|
|
|
|
static int get_space(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
|
|
|
audio_buf_info zz = {0};
|
|
|
|
if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &zz) != -1) {
|
|
|
|
// calculate exact buffer space:
|
|
|
|
return zz.fragments * zz.fragsize / ao->sstride;
|
|
|
|
}
|
|
|
|
|
2014-09-16 22:20:20 +00:00
|
|
|
if (p->audio_fd < 0 && p->device_failed && get_delay(ao) > 0.2)
|
|
|
|
return 0;
|
|
|
|
|
2014-09-16 22:14:21 +00:00
|
|
|
if (p->audio_fd < 0 || device_writable(ao) > 0)
|
|
|
|
return p->outburst / ao->sstride;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-09-15 20:25:01 +00:00
|
|
|
// stop playing, keep buffers (for pause)
|
|
|
|
static void audio_pause(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
p->prepause_samples = get_delay(ao) * ao->samplerate;
|
|
|
|
#if KEEP_DEVICE
|
|
|
|
ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
|
|
|
|
#else
|
|
|
|
close_device(ao);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2014-09-16 22:14:21 +00:00
|
|
|
// resume playing, after audio_pause()
|
|
|
|
static void audio_resume(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
p->audio_end = 0;
|
|
|
|
if (p->prepause_samples > 0)
|
|
|
|
ao_play_silence(ao, p->prepause_samples);
|
|
|
|
}
|
|
|
|
|
2014-11-06 00:17:36 +00:00
|
|
|
static int audio_wait(struct ao *ao, pthread_mutex_t *lock)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
|
|
|
struct pollfd fd = {.fd = p->audio_fd, .events = POLLOUT};
|
|
|
|
int r = ao_wait_poll(ao, &fd, 1, lock);
|
|
|
|
if (fd.revents & (POLLERR | POLLNVAL))
|
|
|
|
return -1;
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2016-06-29 15:40:04 +00:00
|
|
|
static void list_devs(struct ao *ao, struct ao_device_list *list)
|
|
|
|
{
|
|
|
|
if (stat(PATH_DEV_DSP, &(struct stat){0}) == 0)
|
|
|
|
ao_device_list_add(list, ao, &(struct ao_device_desc){"", "Default"});
|
|
|
|
}
|
|
|
|
|
2013-07-21 21:52:40 +00:00
|
|
|
#define OPT_BASE_STRUCT struct priv
|
|
|
|
|
2013-06-07 13:05:20 +00:00
|
|
|
const struct ao_driver audio_out_oss = {
|
2013-10-23 17:07:27 +00:00
|
|
|
.description = "OSS/ioctl audio output",
|
|
|
|
.name = "oss",
|
2013-06-07 13:05:20 +00:00
|
|
|
.init = init,
|
|
|
|
.uninit = uninit,
|
|
|
|
.control = control,
|
|
|
|
.get_space = get_space,
|
|
|
|
.play = play,
|
|
|
|
.get_delay = get_delay,
|
|
|
|
.pause = audio_pause,
|
|
|
|
.resume = audio_resume,
|
|
|
|
.reset = reset,
|
2014-03-08 23:49:39 +00:00
|
|
|
.drain = drain,
|
2014-11-06 00:17:36 +00:00
|
|
|
.wait = audio_wait,
|
|
|
|
.wakeup = ao_wakeup_poll,
|
2016-06-29 15:40:04 +00:00
|
|
|
.list_devs = list_devs,
|
2013-07-21 21:52:40 +00:00
|
|
|
.priv_size = sizeof(struct priv),
|
|
|
|
.priv_defaults = &(const struct priv) {
|
|
|
|
.audio_fd = -1,
|
|
|
|
.audio_delay_method = 2,
|
|
|
|
.buffersize = -1,
|
|
|
|
.outburst = 512,
|
|
|
|
.oss_mixer_channel = SOUND_MIXER_PCM,
|
|
|
|
.oss_mixer_device = PATH_DEV_MIXER,
|
|
|
|
},
|
|
|
|
.options = (const struct m_option[]) {
|
|
|
|
OPT_STRING("mixer-device", oss_mixer_device, 0),
|
|
|
|
OPT_STRING("mixer-channel", cfg_oss_mixer_channel, 0),
|
|
|
|
{0}
|
|
|
|
},
|
2016-11-25 20:00:39 +00:00
|
|
|
.options_prefix = "oss",
|
2013-06-07 13:05:20 +00:00
|
|
|
};
|