2009-01-26 16:06:44 +01:00
|
|
|
/*
|
|
|
|
* OSS audio output driver
|
|
|
|
*
|
|
|
|
* This file is part of MPlayer.
|
|
|
|
*
|
2013-06-07 14:29:59 +02:00
|
|
|
* Support for >2 output channels added 2001-11-25
|
|
|
|
* - Steve Davies <steve@daviesfam.org>
|
|
|
|
*
|
2009-01-26 16:06:44 +01:00
|
|
|
* MPlayer 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.
|
|
|
|
*
|
|
|
|
* MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
*/
|
|
|
|
|
2001-06-03 01:25:43 +02: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>
|
2002-03-19 20:16:01 +01:00
|
|
|
#include <errno.h>
|
|
|
|
#include <string.h>
|
2001-06-03 01:25:43 +02:00
|
|
|
|
2004-12-07 03:24:15 +01:00
|
|
|
#include "config.h"
|
2013-06-07 15:05:20 +02:00
|
|
|
#include "core/options.h"
|
2012-11-09 01:06:43 +01:00
|
|
|
#include "core/mp_msg.h"
|
|
|
|
#include "audio/mixer.h"
|
2001-06-03 01:25:43 +02:00
|
|
|
|
2004-12-27 18:30:15 +01:00
|
|
|
#ifdef HAVE_SYS_SOUNDCARD_H
|
|
|
|
#include <sys/soundcard.h>
|
|
|
|
#else
|
|
|
|
#ifdef HAVE_SOUNDCARD_H
|
|
|
|
#include <soundcard.h>
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
2012-11-09 01:06:43 +01:00
|
|
|
#include "audio/format.h"
|
2001-08-15 19:32:47 +02:00
|
|
|
|
2012-11-09 01:06:43 +01:00
|
|
|
#include "ao.h"
|
2001-06-03 01:25:43 +02:00
|
|
|
|
2004-12-27 18:30:15 +01:00
|
|
|
static int format2oss(int format)
|
|
|
|
{
|
2013-06-07 14:29:59 +02:00
|
|
|
switch (format) {
|
2004-12-27 18:30:15 +01:00
|
|
|
case AF_FORMAT_U8: return AFMT_U8;
|
|
|
|
case AF_FORMAT_S8: return AFMT_S8;
|
|
|
|
case AF_FORMAT_U16_LE: return AFMT_U16_LE;
|
|
|
|
case AF_FORMAT_U16_BE: return AFMT_U16_BE;
|
|
|
|
case AF_FORMAT_S16_LE: return AFMT_S16_LE;
|
|
|
|
case AF_FORMAT_S16_BE: return AFMT_S16_BE;
|
2009-07-24 12:35:22 +02:00
|
|
|
#ifdef AFMT_S24_PACKED
|
|
|
|
case AF_FORMAT_S24_LE: return AFMT_S24_PACKED;
|
2004-12-27 18:30:15 +01:00
|
|
|
#endif
|
2005-01-06 14:15:53 +01:00
|
|
|
#ifdef AFMT_U32_LE
|
2004-12-27 18:30:15 +01:00
|
|
|
case AF_FORMAT_U32_LE: return AFMT_U32_LE;
|
2005-01-06 14:15:53 +01:00
|
|
|
#endif
|
|
|
|
#ifdef AFMT_U32_BE
|
2004-12-27 18:30:15 +01:00
|
|
|
case AF_FORMAT_U32_BE: return AFMT_U32_BE;
|
2005-01-06 14:15:53 +01:00
|
|
|
#endif
|
|
|
|
#ifdef AFMT_S32_LE
|
2004-12-27 18:30:15 +01:00
|
|
|
case AF_FORMAT_S32_LE: return AFMT_S32_LE;
|
2005-01-06 14:15:53 +01:00
|
|
|
#endif
|
|
|
|
#ifdef AFMT_S32_BE
|
2004-12-27 18:30:15 +01:00
|
|
|
case AF_FORMAT_S32_BE: return AFMT_S32_BE;
|
|
|
|
#endif
|
|
|
|
#ifdef AFMT_FLOAT
|
|
|
|
case AF_FORMAT_FLOAT_NE: return AFMT_FLOAT;
|
|
|
|
#endif
|
2013-06-07 14:29:59 +02:00
|
|
|
// SPECIALS
|
2004-12-27 18:30:15 +01:00
|
|
|
#ifdef AFMT_MPEG
|
|
|
|
case AF_FORMAT_MPEG2: return AFMT_MPEG;
|
|
|
|
#endif
|
|
|
|
#ifdef AFMT_AC3
|
2010-01-11 21:27:52 +01:00
|
|
|
case AF_FORMAT_AC3_NE: return AFMT_AC3;
|
2004-12-27 18:30:15 +01:00
|
|
|
#endif
|
|
|
|
}
|
2013-06-07 14:29:59 +02:00
|
|
|
mp_msg(MSGT_AO, MSGL_V, "OSS: Unknown/not supported internal format: %s\n",
|
|
|
|
af_fmt2str_short(format));
|
2004-12-27 18:30:15 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int oss2format(int format)
|
|
|
|
{
|
2013-06-07 14:29:59 +02:00
|
|
|
switch (format) {
|
2004-12-27 18:30:15 +01:00
|
|
|
case AFMT_U8: return AF_FORMAT_U8;
|
|
|
|
case AFMT_S8: return AF_FORMAT_S8;
|
|
|
|
case AFMT_U16_LE: return AF_FORMAT_U16_LE;
|
|
|
|
case AFMT_U16_BE: return AF_FORMAT_U16_BE;
|
|
|
|
case AFMT_S16_LE: return AF_FORMAT_S16_LE;
|
|
|
|
case AFMT_S16_BE: return AF_FORMAT_S16_BE;
|
2009-07-24 12:35:22 +02:00
|
|
|
#ifdef AFMT_S24_PACKED
|
|
|
|
case AFMT_S24_PACKED: return AF_FORMAT_S24_LE;
|
2004-12-27 18:30:15 +01:00
|
|
|
#endif
|
2005-01-06 14:15:53 +01:00
|
|
|
#ifdef AFMT_U32_LE
|
2004-12-27 18:30:15 +01:00
|
|
|
case AFMT_U32_LE: return AF_FORMAT_U32_LE;
|
2005-01-06 14:15:53 +01:00
|
|
|
#endif
|
|
|
|
#ifdef AFMT_U32_BE
|
2004-12-27 18:30:15 +01:00
|
|
|
case AFMT_U32_BE: return AF_FORMAT_U32_BE;
|
2005-01-06 14:15:53 +01:00
|
|
|
#endif
|
|
|
|
#ifdef AFMT_S32_LE
|
2004-12-27 18:30:15 +01:00
|
|
|
case AFMT_S32_LE: return AF_FORMAT_S32_LE;
|
2005-01-06 14:15:53 +01:00
|
|
|
#endif
|
|
|
|
#ifdef AFMT_S32_BE
|
2004-12-27 18:30:15 +01:00
|
|
|
case AFMT_S32_BE: return AF_FORMAT_S32_BE;
|
|
|
|
#endif
|
|
|
|
#ifdef AFMT_FLOAT
|
|
|
|
case AFMT_FLOAT: return AF_FORMAT_FLOAT_NE;
|
|
|
|
#endif
|
2013-06-07 14:29:59 +02:00
|
|
|
// SPECIALS
|
2004-12-27 18:30:15 +01:00
|
|
|
#ifdef AFMT_MPEG
|
|
|
|
case AFMT_MPEG: return AF_FORMAT_MPEG2;
|
|
|
|
#endif
|
|
|
|
#ifdef AFMT_AC3
|
2010-01-11 21:27:52 +01:00
|
|
|
case AFMT_AC3: return AF_FORMAT_AC3_NE;
|
2004-12-27 18:30:15 +01:00
|
|
|
#endif
|
|
|
|
}
|
2013-06-07 14:29:59 +02:00
|
|
|
mp_tmsg(MSGT_GLOBAL, MSGL_ERR, "[AO OSS] Unknown/Unsupported OSS format: %x.\n",
|
|
|
|
format);
|
2004-12-27 18:30:15 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-06-07 14:29:59 +02:00
|
|
|
static char *dsp = PATH_DEV_DSP;
|
2001-06-03 01:25:43 +02:00
|
|
|
static audio_buf_info zz;
|
2013-06-07 14:29:59 +02:00
|
|
|
static int audio_fd = -1;
|
2007-07-19 21:15:59 +02:00
|
|
|
static int prepause_space;
|
2001-06-03 01:25:43 +02:00
|
|
|
|
2006-07-09 05:50:47 +02:00
|
|
|
static const char *oss_mixer_device = PATH_DEV_MIXER;
|
|
|
|
static int oss_mixer_channel = SOUND_MIXER_PCM;
|
2001-06-22 01:07:15 +02:00
|
|
|
|
2009-06-23 06:31:13 +02:00
|
|
|
#ifdef SNDCTL_DSP_GETPLAYVOL
|
2013-06-07 14:29:59 +02:00
|
|
|
static int volume_oss4(ao_control_vol_t *vol, int cmd)
|
|
|
|
{
|
2009-06-23 06:31:13 +02:00
|
|
|
int v;
|
|
|
|
|
|
|
|
if (audio_fd < 0)
|
|
|
|
return CONTROL_ERROR;
|
|
|
|
|
|
|
|
if (cmd == AOCONTROL_GET_VOLUME) {
|
|
|
|
if (ioctl(audio_fd, SNDCTL_DSP_GETPLAYVOL, &v) == -1)
|
|
|
|
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;
|
|
|
|
if (ioctl(audio_fd, SNDCTL_DSP_SETPLAYVOL, &v) == -1)
|
|
|
|
return CONTROL_ERROR;
|
|
|
|
return CONTROL_OK;
|
|
|
|
} else
|
|
|
|
return CONTROL_UNKNOWN;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2001-06-03 01:25:43 +02:00
|
|
|
// to set/get/query special features/parameters
|
2013-06-07 15:05:20 +02:00
|
|
|
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
2013-06-07 14:29:59 +02:00
|
|
|
{
|
|
|
|
switch (cmd) {
|
|
|
|
case AOCONTROL_GET_VOLUME:
|
|
|
|
case AOCONTROL_SET_VOLUME:
|
|
|
|
{
|
|
|
|
ao_control_vol_t *vol = (ao_control_vol_t *)arg;
|
|
|
|
int fd, v, devs;
|
2001-08-15 13:50:55 +02:00
|
|
|
|
2009-06-23 06:31:13 +02:00
|
|
|
#ifdef SNDCTL_DSP_GETPLAYVOL
|
|
|
|
// Try OSS4 first
|
|
|
|
if (volume_oss4(vol, cmd) == CONTROL_OK)
|
|
|
|
return CONTROL_OK;
|
|
|
|
#endif
|
|
|
|
|
2013-06-07 15:05:20 +02:00
|
|
|
if (AF_FORMAT_IS_AC3(ao->format))
|
2013-06-07 14:29:59 +02:00
|
|
|
return CONTROL_TRUE;
|
|
|
|
|
|
|
|
if ((fd = open(oss_mixer_device, O_RDONLY)) != -1) {
|
|
|
|
ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devs);
|
|
|
|
if (devs & (1 << oss_mixer_channel)) {
|
|
|
|
if (cmd == AOCONTROL_GET_VOLUME) {
|
|
|
|
ioctl(fd, MIXER_READ(oss_mixer_channel), &v);
|
|
|
|
vol->right = (v & 0xFF00) >> 8;
|
|
|
|
vol->left = v & 0x00FF;
|
|
|
|
} else {
|
|
|
|
v = ((int)vol->right << 8) | (int)vol->left;
|
|
|
|
ioctl(fd, MIXER_WRITE(oss_mixer_channel), &v);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
close(fd);
|
|
|
|
return CONTROL_ERROR;
|
|
|
|
}
|
|
|
|
close(fd);
|
|
|
|
return CONTROL_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return CONTROL_ERROR;
|
2001-06-03 01:25:43 +02:00
|
|
|
}
|
|
|
|
return CONTROL_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
|
|
|
// open & setup audio device
|
2013-06-07 15:05:20 +02:00
|
|
|
// return: 0=success -1=fail
|
|
|
|
static int init(struct ao *ao, char *params)
|
2013-06-07 14:29:59 +02:00
|
|
|
{
|
2013-06-07 15:05:20 +02:00
|
|
|
struct MPOpts *opts = ao->opts;
|
2013-06-07 14:29:59 +02:00
|
|
|
char *mixer_channels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
|
|
|
|
int oss_format;
|
2013-06-07 15:05:20 +02:00
|
|
|
char *mdev = opts->mixer_device, *mchan = opts->mixer_channel;
|
2013-06-07 14:29:59 +02:00
|
|
|
|
2013-06-07 15:05:20 +02:00
|
|
|
mp_msg(MSGT_AO, MSGL_V, "ao2: %d Hz %d chans %s\n", ao->samplerate,
|
|
|
|
ao->channels.num, af_fmt2str_short(ao->format));
|
2013-06-07 14:29:59 +02:00
|
|
|
|
|
|
|
if (ao_subdevice) {
|
|
|
|
char *m, *c;
|
|
|
|
m = strchr(ao_subdevice, ':');
|
|
|
|
if (m) {
|
|
|
|
c = strchr(m + 1, ':');
|
|
|
|
if (c) {
|
|
|
|
mchan = c + 1;
|
|
|
|
c[0] = '\0';
|
|
|
|
}
|
|
|
|
mdev = m + 1;
|
|
|
|
m[0] = '\0';
|
|
|
|
}
|
|
|
|
dsp = ao_subdevice;
|
2005-11-10 10:02:56 +01:00
|
|
|
}
|
2013-06-07 14:29:59 +02:00
|
|
|
|
|
|
|
if (mdev)
|
|
|
|
oss_mixer_device = mdev;
|
|
|
|
else
|
|
|
|
oss_mixer_device = PATH_DEV_MIXER;
|
|
|
|
|
|
|
|
if (mchan) {
|
|
|
|
int fd, devs, i;
|
|
|
|
|
|
|
|
if ((fd = open(oss_mixer_device, O_RDONLY)) == -1) {
|
|
|
|
mp_tmsg(MSGT_AO, MSGL_ERR,
|
|
|
|
"[AO OSS] audio_setup: Can't open mixer device %s: %s\n",
|
|
|
|
oss_mixer_device, strerror(errno));
|
|
|
|
} 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_tmsg(MSGT_AO, MSGL_ERR, "[AO OSS] audio_setup: "
|
|
|
|
"Audio card mixer does not have channel '%s', "
|
|
|
|
"using default.\n", mchan);
|
|
|
|
i = SOUND_MIXER_NRDEVICES + 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
oss_mixer_channel = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i == SOUND_MIXER_NRDEVICES) {
|
|
|
|
mp_tmsg(MSGT_AO, MSGL_ERR, "[AO OSS] audio_setup: Audio card "
|
|
|
|
"mixer does not have channel '%s', using default.\n",
|
|
|
|
mchan);
|
|
|
|
}
|
2004-01-24 12:53:52 +01:00
|
|
|
}
|
2013-06-07 14:29:59 +02:00
|
|
|
} else {
|
|
|
|
oss_mixer_channel = SOUND_MIXER_PCM;
|
2004-01-24 12:53:52 +01:00
|
|
|
}
|
|
|
|
|
2013-06-07 14:29:59 +02:00
|
|
|
mp_msg(MSGT_AO, MSGL_V, "audio_setup: using '%s' dsp device\n", dsp);
|
|
|
|
mp_msg(MSGT_AO, MSGL_V, "audio_setup: using '%s' mixer device\n",
|
|
|
|
oss_mixer_device);
|
|
|
|
mp_msg(MSGT_AO, MSGL_V, "audio_setup: using '%s' mixer device\n",
|
|
|
|
mixer_channels[oss_mixer_channel]);
|
2001-06-22 01:07:15 +02:00
|
|
|
|
2002-04-29 22:42:15 +02:00
|
|
|
#ifdef __linux__
|
2013-06-07 14:29:59 +02:00
|
|
|
audio_fd = open(dsp, O_WRONLY | O_NONBLOCK);
|
2002-04-29 22:42:15 +02:00
|
|
|
#else
|
2013-06-07 14:29:59 +02:00
|
|
|
audio_fd = open(dsp, O_WRONLY);
|
2002-04-29 22:42:15 +02:00
|
|
|
#endif
|
2013-06-07 14:29:59 +02:00
|
|
|
if (audio_fd < 0) {
|
|
|
|
mp_tmsg(MSGT_AO, MSGL_ERR,
|
|
|
|
"[AO OSS] audio_setup: Can't open audio device %s: %s\n",
|
|
|
|
dsp, strerror(errno));
|
2013-06-07 15:05:20 +02:00
|
|
|
return -1;
|
2013-06-07 14:29:59 +02:00
|
|
|
}
|
2001-06-03 01:25:43 +02:00
|
|
|
|
2002-04-29 22:42:15 +02:00
|
|
|
#ifdef __linux__
|
2013-06-07 14:29:59 +02:00
|
|
|
/* Remove the non-blocking flag */
|
|
|
|
if (fcntl(audio_fd, F_SETFL, 0) < 0) {
|
|
|
|
mp_tmsg(MSGT_AO, MSGL_ERR, "[AO OSS] audio_setup: Can't make file "
|
|
|
|
"descriptor blocking: %s\n", strerror(errno));
|
2013-06-07 15:05:20 +02:00
|
|
|
return -1;
|
2013-06-07 14:29:59 +02:00
|
|
|
}
|
2002-04-29 22:42:15 +02:00
|
|
|
#endif
|
2002-11-28 17:14:08 +01:00
|
|
|
|
|
|
|
#if defined(FD_CLOEXEC) && defined(F_SETFD)
|
2013-06-07 14:29:59 +02:00
|
|
|
fcntl(audio_fd, F_SETFD, FD_CLOEXEC);
|
2002-11-28 17:14:08 +01:00
|
|
|
#endif
|
2009-07-07 01:26:13 +02:00
|
|
|
|
2013-06-07 15:05:20 +02:00
|
|
|
if (AF_FORMAT_IS_AC3(ao->format)) {
|
|
|
|
ioctl(audio_fd, SNDCTL_DSP_SPEED, &ao->samplerate);
|
2013-06-07 14:29:59 +02:00
|
|
|
}
|
2002-04-23 00:33:06 +02:00
|
|
|
|
2009-07-07 01:26:13 +02:00
|
|
|
ac3_retry:
|
2013-06-07 15:05:20 +02:00
|
|
|
if (AF_FORMAT_IS_AC3(ao->format))
|
|
|
|
ao->format = AF_FORMAT_AC3_NE;
|
|
|
|
oss_format = format2oss(ao->format);
|
2013-06-07 14:29:59 +02:00
|
|
|
if (oss_format == -1) {
|
Remove compile time/runtime CPU detection, and drop some platforms
mplayer had three ways of enabling CPU specific assembler routines:
a) Enable them at compile time; crash if the CPU can't handle it.
b) Enable them at compile time, but let the configure script detect
your CPU. Your binary will only crash if you try to run it on a
different system that has less features than yours.
This was the default, I think.
c) Runtime detection.
The implementation of b) and c) suck. a) is not really feasible (it
sucks for users). Remove all code related to this, and use libav's CPU
detection instead. Now the configure script will always enable CPU
specific features, and disable them at runtime if libav reports them
not as available.
One implication is that now the compiler is always expected to handle
SSE (etc.) inline assembly at runtime, unless it's explicitly disabled.
Only checks for x86 CPU specific features are kept, the rest is either
unused or barely used.
Get rid of all the dump -mpcu, -march etc. flags. Trust the compiler
to select decent settings.
Get rid of support for the following operating systems:
- BSD/OS (some ancient BSD fork)
- QNX (don't care)
- BeOS (dead, Haiku support is still welcome)
- AIX (don't care)
- HP-UX (don't care)
- OS/2 (dead, actual support has been removed a while ago)
Remove the configure code for detecting the endianness. Instead, use
the standard header <endian.h>, which can be used if _GNU_SOURCE or
_BSD_SOURCE is defined. (Maybe these changes should have been in a
separate commit.)
Since this is a quite violent code removal orgy, and I'm testing only
on x86 32 bit Linux, expect regressions.
2012-07-29 17:20:57 +02:00
|
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
2013-06-07 14:29:59 +02:00
|
|
|
oss_format = AFMT_S16_BE;
|
2004-12-28 02:59:12 +01:00
|
|
|
#else
|
2013-06-07 14:29:59 +02:00
|
|
|
oss_format = AFMT_S16_LE;
|
2004-12-28 02:59:12 +01:00
|
|
|
#endif
|
2013-06-07 15:05:20 +02:00
|
|
|
ao->format = AF_FORMAT_S16_NE;
|
2013-06-07 14:29:59 +02:00
|
|
|
}
|
|
|
|
if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &oss_format) < 0 ||
|
2013-06-07 15:05:20 +02:00
|
|
|
oss_format != format2oss(ao->format))
|
2013-06-07 14:29:59 +02:00
|
|
|
{
|
|
|
|
mp_tmsg(MSGT_AO, MSGL_WARN, "[AO OSS] Can't set audio device %s to %s "
|
2013-06-07 15:05:20 +02:00
|
|
|
"output, trying %s...\n", dsp, af_fmt2str_short(ao->format),
|
2013-06-07 14:29:59 +02:00
|
|
|
af_fmt2str_short(AF_FORMAT_S16_NE));
|
2013-06-07 15:05:20 +02:00
|
|
|
ao->format = AF_FORMAT_S16_NE;
|
2013-06-07 14:29:59 +02:00
|
|
|
goto ac3_retry;
|
2001-11-28 13:46:23 +01:00
|
|
|
}
|
2013-06-07 14:29:59 +02:00
|
|
|
|
2013-06-07 15:05:20 +02:00
|
|
|
ao->format = oss2format(oss_format);
|
|
|
|
if (ao->format == -1)
|
|
|
|
return -1;
|
2013-06-07 14:29:59 +02:00
|
|
|
|
2013-06-07 15:05:20 +02:00
|
|
|
mp_msg(MSGT_AO, MSGL_V, "audio_setup: sample format: %s\n",
|
|
|
|
af_fmt2str_short(ao->format));
|
2013-06-07 14:29:59 +02:00
|
|
|
|
2013-06-07 15:05:20 +02:00
|
|
|
if (!AF_FORMAT_IS_AC3(ao->format)) {
|
2013-06-07 14:29:59 +02:00
|
|
|
struct mp_chmap_sel sel = {0};
|
|
|
|
mp_chmap_sel_add_alsa_def(&sel);
|
2013-06-07 15:05:20 +02:00
|
|
|
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
|
|
|
return -1;
|
|
|
|
int reqchannels = ao->channels.num;
|
2013-06-07 14:29:59 +02:00
|
|
|
// We only use SNDCTL_DSP_CHANNELS for >2 channels, in case some drivers don't have it
|
|
|
|
if (reqchannels > 2) {
|
|
|
|
int nchannels = reqchannels;
|
|
|
|
if (ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &nchannels) == -1 ||
|
|
|
|
nchannels != reqchannels)
|
|
|
|
{
|
|
|
|
mp_tmsg(MSGT_AO, MSGL_ERR, "[AO OSS] audio_setup: Failed to "
|
|
|
|
"set audio device to %d channels.\n", reqchannels);
|
2013-06-07 15:05:20 +02:00
|
|
|
return -1;
|
2013-06-07 14:29:59 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
int c = reqchannels - 1;
|
|
|
|
if (ioctl(audio_fd, SNDCTL_DSP_STEREO, &c) == -1) {
|
|
|
|
mp_tmsg(MSGT_AO, MSGL_ERR, "[AO OSS] audio_setup: Failed to "
|
|
|
|
"set audio device to %d channels.\n", reqchannels);
|
2013-06-07 15:05:20 +02:00
|
|
|
return -1;
|
2013-06-07 14:29:59 +02:00
|
|
|
}
|
2013-06-07 15:05:20 +02:00
|
|
|
if (!ao_chmap_sel_get_def(ao, &sel, &ao->channels, c + 1))
|
|
|
|
return -1;
|
2013-06-07 14:29:59 +02:00
|
|
|
}
|
|
|
|
mp_msg(MSGT_AO, MSGL_V,
|
|
|
|
"audio_setup: using %d channels (requested: %d)\n",
|
2013-06-07 15:05:20 +02:00
|
|
|
ao->channels.num, reqchannels);
|
2013-06-07 14:29:59 +02:00
|
|
|
// set rate
|
2013-06-07 15:05:20 +02:00
|
|
|
ioctl(audio_fd, SNDCTL_DSP_SPEED, &ao->samplerate);
|
|
|
|
mp_msg(MSGT_AO, MSGL_V, "audio_setup: using %d Hz samplerate\n",
|
|
|
|
ao->samplerate);
|
2013-06-07 14:29:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &zz) == -1) {
|
|
|
|
int r = 0;
|
|
|
|
mp_tmsg(MSGT_AO, MSGL_WARN, "[AO OSS] audio_setup: driver doesn't "
|
|
|
|
"support SNDCTL_DSP_GETOSPACE\n");
|
|
|
|
if (ioctl(audio_fd, SNDCTL_DSP_GETBLKSIZE, &r) == -1)
|
|
|
|
mp_msg(MSGT_AO, MSGL_V, "audio_setup: %d bytes/frag (config.h)\n",
|
2013-06-07 15:05:20 +02:00
|
|
|
ao->outburst);
|
2013-06-07 14:29:59 +02:00
|
|
|
else {
|
2013-06-07 15:05:20 +02:00
|
|
|
ao->outburst = r;
|
2013-06-07 14:29:59 +02:00
|
|
|
mp_msg(MSGT_AO, MSGL_V, "audio_setup: %d bytes/frag (GETBLKSIZE)\n",
|
2013-06-07 15:05:20 +02:00
|
|
|
ao->outburst);
|
2013-06-07 14:29:59 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
mp_msg(MSGT_AO, MSGL_V,
|
|
|
|
"audio_setup: frags: %3d/%d (%d bytes/frag) free: %6d\n",
|
|
|
|
zz.fragments, zz.fragstotal, zz.fragsize, zz.bytes);
|
2013-06-07 15:05:20 +02:00
|
|
|
if (ao->buffersize == -1)
|
|
|
|
ao->buffersize = zz.bytes;
|
|
|
|
ao->outburst = zz.fragsize;
|
2001-11-28 13:46:23 +01:00
|
|
|
}
|
2013-06-07 14:29:59 +02:00
|
|
|
|
2013-06-07 15:05:20 +02:00
|
|
|
if (ao->buffersize == -1) {
|
2013-06-07 14:29:59 +02:00
|
|
|
// Measuring buffer size:
|
|
|
|
void *data;
|
2013-06-07 15:05:20 +02:00
|
|
|
ao->buffersize = 0;
|
2001-06-03 01:25:43 +02:00
|
|
|
#ifdef HAVE_AUDIO_SELECT
|
2013-06-07 15:05:20 +02:00
|
|
|
data = malloc(ao->outburst);
|
|
|
|
memset(data, 0, ao->outburst);
|
|
|
|
while (ao->buffersize < 0x40000) {
|
2013-06-07 14:29:59 +02:00
|
|
|
fd_set rfds;
|
|
|
|
struct timeval tv;
|
|
|
|
FD_ZERO(&rfds);
|
|
|
|
FD_SET(audio_fd, &rfds);
|
|
|
|
tv.tv_sec = 0;
|
|
|
|
tv.tv_usec = 0;
|
|
|
|
if (!select(audio_fd + 1, NULL, &rfds, NULL, &tv))
|
|
|
|
break;
|
2013-06-07 15:05:20 +02:00
|
|
|
write(audio_fd, data, ao->outburst);
|
|
|
|
ao->buffersize += ao->outburst;
|
2013-06-07 14:29:59 +02:00
|
|
|
}
|
|
|
|
free(data);
|
2013-06-07 15:05:20 +02:00
|
|
|
if (ao->buffersize == 0) {
|
2013-06-07 14:29:59 +02:00
|
|
|
mp_tmsg(MSGT_AO, MSGL_ERR, "[AO OSS]\n *** Your audio driver "
|
|
|
|
"DOES NOT support select() ***\n Recompile mpv with "
|
|
|
|
"#undef HAVE_AUDIO_SELECT in config.h !\n\n");
|
2013-06-07 15:05:20 +02:00
|
|
|
return -1;
|
2013-06-07 14:29:59 +02:00
|
|
|
}
|
|
|
|
#endif
|
2001-06-03 01:25:43 +02:00
|
|
|
}
|
2013-06-07 14:29:59 +02:00
|
|
|
|
2013-06-07 15:05:20 +02:00
|
|
|
ao->bps = ao->channels.num * (af_fmt2bits(ao->format) / 8);
|
|
|
|
ao->outburst -= ao->outburst % ao->bps; // round down
|
|
|
|
ao->bps *= ao->samplerate;
|
2002-05-13 22:11:51 +02:00
|
|
|
|
2013-06-07 15:05:20 +02:00
|
|
|
return 0;
|
2001-06-03 01:25:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// close audio device
|
2013-06-07 15:05:20 +02:00
|
|
|
static void uninit(struct ao *ao, bool immed)
|
2013-06-07 14:29:59 +02:00
|
|
|
{
|
|
|
|
if (audio_fd == -1)
|
|
|
|
return;
|
2004-04-06 19:55:36 +02:00
|
|
|
#ifdef SNDCTL_DSP_SYNC
|
|
|
|
// to get the buffer played
|
|
|
|
if (!immed)
|
2013-06-07 14:29:59 +02:00
|
|
|
ioctl(audio_fd, SNDCTL_DSP_SYNC, NULL);
|
2004-04-06 19:55:36 +02:00
|
|
|
#endif
|
2001-06-05 04:26:56 +02:00
|
|
|
#ifdef SNDCTL_DSP_RESET
|
2004-04-06 19:55:36 +02:00
|
|
|
if (immed)
|
2013-06-07 14:29:59 +02:00
|
|
|
ioctl(audio_fd, SNDCTL_DSP_RESET, NULL);
|
2001-06-05 04:26:56 +02:00
|
|
|
#endif
|
2001-06-03 01:25:43 +02:00
|
|
|
close(audio_fd);
|
2002-08-28 21:37:13 +02:00
|
|
|
audio_fd = -1;
|
2001-06-03 01:25:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// stop playing and empty buffers (for seeking/pause)
|
2013-06-07 15:05:20 +02:00
|
|
|
static void reset(struct ao *ao)
|
2013-06-07 14:29:59 +02:00
|
|
|
{
|
|
|
|
int oss_format;
|
2013-06-07 15:05:20 +02:00
|
|
|
uninit(ao, true);
|
2013-06-07 14:29:59 +02:00
|
|
|
audio_fd = open(dsp, O_WRONLY);
|
|
|
|
if (audio_fd < 0) {
|
|
|
|
mp_tmsg(MSGT_AO, MSGL_ERR, "[AO OSS]\nFatal error: *** CANNOT "
|
|
|
|
"RE-OPEN / RESET AUDIO DEVICE *** %s\n", strerror(errno));
|
|
|
|
return;
|
2001-06-03 01:25:43 +02:00
|
|
|
}
|
|
|
|
|
2003-08-13 23:04:15 +02:00
|
|
|
#if defined(FD_CLOEXEC) && defined(F_SETFD)
|
2013-06-07 14:29:59 +02:00
|
|
|
fcntl(audio_fd, F_SETFD, FD_CLOEXEC);
|
|
|
|
#endif
|
|
|
|
|
2013-06-07 15:05:20 +02:00
|
|
|
oss_format = format2oss(ao->format);
|
|
|
|
if (AF_FORMAT_IS_AC3(ao->format))
|
|
|
|
ioctl(audio_fd, SNDCTL_DSP_SPEED, &ao->samplerate);
|
2013-06-07 14:29:59 +02:00
|
|
|
ioctl(audio_fd, SNDCTL_DSP_SETFMT, &oss_format);
|
2013-06-07 15:05:20 +02:00
|
|
|
if (!AF_FORMAT_IS_AC3(ao->format)) {
|
|
|
|
if (ao->channels.num > 2)
|
|
|
|
ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &ao->channels.num);
|
2013-06-07 14:29:59 +02:00
|
|
|
else {
|
2013-06-07 15:05:20 +02:00
|
|
|
int c = ao->channels.num - 1;
|
2013-06-07 14:29:59 +02:00
|
|
|
ioctl(audio_fd, SNDCTL_DSP_STEREO, &c);
|
|
|
|
}
|
2013-06-07 15:05:20 +02:00
|
|
|
ioctl(audio_fd, SNDCTL_DSP_SPEED, &ao->samplerate);
|
2007-07-19 21:15:59 +02:00
|
|
|
}
|
2001-06-05 20:40:44 +02:00
|
|
|
}
|
|
|
|
|
2001-06-03 01:25:43 +02:00
|
|
|
// return: how many bytes can be played without blocking
|
2013-06-07 15:05:20 +02:00
|
|
|
static int get_space(struct ao *ao)
|
2013-06-07 14:29:59 +02:00
|
|
|
{
|
2013-06-07 15:05:20 +02:00
|
|
|
int playsize = ao->outburst;
|
2001-06-03 01:25:43 +02:00
|
|
|
|
2001-06-05 04:26:56 +02:00
|
|
|
#ifdef SNDCTL_DSP_GETOSPACE
|
2013-06-07 14:29:59 +02:00
|
|
|
if (ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &zz) != -1) {
|
|
|
|
// calculate exact buffer space:
|
|
|
|
playsize = zz.fragments * zz.fragsize;
|
|
|
|
return playsize;
|
|
|
|
}
|
2001-06-05 04:26:56 +02:00
|
|
|
#endif
|
2001-06-03 01:25:43 +02:00
|
|
|
|
|
|
|
// check buffer
|
|
|
|
#ifdef HAVE_AUDIO_SELECT
|
2013-06-07 14:29:59 +02:00
|
|
|
{
|
|
|
|
fd_set rfds;
|
|
|
|
struct timeval tv;
|
|
|
|
FD_ZERO(&rfds);
|
|
|
|
FD_SET(audio_fd, &rfds);
|
|
|
|
tv.tv_sec = 0;
|
|
|
|
tv.tv_usec = 0;
|
|
|
|
if (!select(audio_fd + 1, NULL, &rfds, NULL, &tv))
|
|
|
|
return 0; // not block!
|
2001-06-03 01:25:43 +02:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2013-06-07 15:05:20 +02:00
|
|
|
return ao->outburst;
|
|
|
|
}
|
|
|
|
|
|
|
|
// stop playing, keep buffers (for pause)
|
|
|
|
static void audio_pause(struct ao *ao)
|
|
|
|
{
|
|
|
|
prepause_space = get_space(ao);
|
|
|
|
uninit(ao, true);
|
2001-06-03 01:25:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// plays 'len' bytes of 'data'
|
|
|
|
// it should round it down to outburst*n
|
|
|
|
// return: number of bytes played
|
2013-06-07 15:05:20 +02:00
|
|
|
static int play(struct ao *ao, void *data, int len, int flags)
|
2013-06-07 14:29:59 +02:00
|
|
|
{
|
|
|
|
if (len == 0)
|
2006-06-28 21:22:27 +02:00
|
|
|
return len;
|
2013-06-07 15:05:20 +02:00
|
|
|
if (len > ao->outburst || !(flags & AOPLAY_FINAL_CHUNK)) {
|
|
|
|
len /= ao->outburst;
|
|
|
|
len *= ao->outburst;
|
2006-06-28 21:22:27 +02:00
|
|
|
}
|
2013-06-07 14:29:59 +02:00
|
|
|
len = write(audio_fd, data, len);
|
2001-06-03 01:25:43 +02:00
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2013-06-07 15:05:20 +02:00
|
|
|
// resume playing, after audio_pause()
|
|
|
|
static void audio_resume(struct ao *ao)
|
|
|
|
{
|
|
|
|
int fillcnt;
|
|
|
|
reset(ao);
|
|
|
|
fillcnt = get_space(ao) - prepause_space;
|
|
|
|
if (fillcnt > 0 && !(ao->format & AF_FORMAT_SPECIAL_MASK)) {
|
|
|
|
void *silence = calloc(fillcnt, 1);
|
|
|
|
play(ao, silence, fillcnt, 0);
|
|
|
|
free(silence);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-07 14:29:59 +02:00
|
|
|
static int audio_delay_method = 2;
|
2001-06-03 01:25:43 +02:00
|
|
|
|
2001-11-24 06:21:22 +01:00
|
|
|
// return: delay in seconds between first and last sample in buffer
|
2013-06-07 15:05:20 +02:00
|
|
|
static float get_delay(struct ao *ao)
|
2013-06-07 14:29:59 +02:00
|
|
|
{
|
|
|
|
/* Calculate how many bytes/second is sent out */
|
|
|
|
if (audio_delay_method == 2) {
|
2002-04-28 00:42:27 +02:00
|
|
|
#ifdef SNDCTL_DSP_GETODELAY
|
2013-06-07 14:29:59 +02:00
|
|
|
int r = 0;
|
|
|
|
if (ioctl(audio_fd, SNDCTL_DSP_GETODELAY, &r) != -1)
|
2013-06-07 15:05:20 +02:00
|
|
|
return ((float)r) / (float)ao->bps;
|
2002-04-28 00:42:27 +02:00
|
|
|
#endif
|
2013-06-07 14:29:59 +02:00
|
|
|
audio_delay_method = 1; // fallback if not supported
|
|
|
|
}
|
|
|
|
if (audio_delay_method == 1) {
|
|
|
|
// SNDCTL_DSP_GETOSPACE
|
|
|
|
if (ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &zz) != -1) {
|
2013-06-07 15:05:20 +02:00
|
|
|
return ((float)(ao->buffersize -
|
|
|
|
zz.bytes)) / (float)ao->bps;
|
2013-06-07 14:29:59 +02:00
|
|
|
}
|
|
|
|
audio_delay_method = 0; // fallback if not supported
|
|
|
|
}
|
2013-06-07 15:05:20 +02:00
|
|
|
return ((float)ao->buffersize) / (float)ao->bps;
|
2001-06-03 01:25:43 +02:00
|
|
|
}
|
2013-06-07 15:05:20 +02:00
|
|
|
|
|
|
|
const struct ao_driver audio_out_oss = {
|
|
|
|
.info = &(const struct ao_info) {
|
|
|
|
"OSS/ioctl audio output",
|
|
|
|
"oss",
|
|
|
|
"A'rpi",
|
|
|
|
""
|
|
|
|
},
|
|
|
|
.init = init,
|
|
|
|
.uninit = uninit,
|
|
|
|
.control = control,
|
|
|
|
.get_space = get_space,
|
|
|
|
.play = play,
|
|
|
|
.get_delay = get_delay,
|
|
|
|
.pause = audio_pause,
|
|
|
|
.resume = audio_resume,
|
|
|
|
.reset = reset,
|
|
|
|
};
|