mpv/audio/out/ao_oss.c

627 lines
18 KiB
C
Raw Normal View History

/*
* OSS audio output driver
*
* This file is part of MPlayer.
*
* 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>
*
* 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.
*/
#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>
#include <errno.h>
#include <string.h>
#include "config.h"
#include "options/options.h"
#include "common/msg.h"
#if HAVE_SYS_SOUNDCARD_H
#include <sys/soundcard.h>
#else
#if HAVE_SOUNDCARD_H
#include <soundcard.h>
#endif
#endif
#include "audio/format.h"
#include "ao.h"
#include "internal.h"
2013-06-07 13:20:07 +00:00
struct priv {
int audio_fd;
int prepause_space;
int oss_mixer_channel;
audio_buf_info zz;
int audio_delay_method;
int buffersize;
int outburst;
2013-07-21 21:52:40 +00:00
char *dsp;
char *oss_mixer_device;
char *cfg_oss_mixer_channel;
2013-06-07 13:20:07 +00:00
};
2013-07-21 21:52:40 +00:00
static const char *mixer_channels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
/* 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
};
static int format_table[][2] = {
{AFMT_U8, AF_FORMAT_U8},
{AFMT_S8, AF_FORMAT_S8},
{AFMT_U16_LE, AF_FORMAT_U16_LE},
{AFMT_U16_BE, AF_FORMAT_U16_BE},
{AFMT_S16_LE, AF_FORMAT_S16_LE},
{AFMT_S16_BE, AF_FORMAT_S16_BE},
#ifdef AFMT_S24_PACKED
{AFMT_S24_PACKED, AF_FORMAT_S24_LE},
#endif
2013-12-03 20:56:03 +00:00
#ifdef AFMT_U24_LE
{AFMT_U24_LE, AF_FORMAT_U24_LE},
#endif
#ifdef AFMT_U24_BE
{AFMT_U24_BE, AF_FORMAT_U24_BE},
#endif
#ifdef AFMT_S24_LE
{AFMT_S24_LE, AF_FORMAT_S24_LE},
#endif
#ifdef AFMT_S24_BE
{AFMT_S24_BE, AF_FORMAT_S24_BE},
#endif
#ifdef AFMT_U32_LE
{AFMT_U32_LE, AF_FORMAT_U32_LE},
#endif
#ifdef AFMT_U32_BE
{AFMT_U32_BE, AF_FORMAT_U32_BE},
#endif
#ifdef AFMT_S32_LE
{AFMT_S32_LE, AF_FORMAT_S32_LE},
#endif
#ifdef AFMT_S32_BE
{AFMT_S32_BE, AF_FORMAT_S32_BE},
#endif
#ifdef AFMT_FLOAT
{AFMT_FLOAT, AF_FORMAT_FLOAT},
#endif
// SPECIALS
#ifdef AFMT_MPEG
{AFMT_MPEG, AF_FORMAT_MPEG2},
#endif
#ifdef AFMT_AC3
{AFMT_AC3, AF_FORMAT_AC3},
#endif
{-1, -1}
};
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];
}
return -1;
}
static int oss2format(int format)
{
for (int n = 0; format_table[n][0] != -1; n++) {
if (format_table[n][0] == format)
return format_table[n][1];
}
return -1;
}
#ifdef SNDCTL_DSP_GETPLAYVOL
static int volume_oss4(struct ao *ao, ao_control_vol_t *vol, int cmd)
2013-06-07 12:29:59 +00:00
{
struct priv *p = ao->priv;
int v;
2013-06-07 13:20:07 +00:00
if (p->audio_fd < 0)
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)
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)
return CONTROL_ERROR;
return CONTROL_OK;
} else
return CONTROL_UNKNOWN;
}
#endif
// 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:
case AOCONTROL_SET_VOLUME:
{
ao_control_vol_t *vol = (ao_control_vol_t *)arg;
int fd, v, devs;
#ifdef SNDCTL_DSP_GETPLAYVOL
// Try OSS4 first
if (volume_oss4(ao, vol, cmd) == CONTROL_OK)
return CONTROL_OK;
#endif
2013-06-07 13:05:20 +00:00
if (AF_FORMAT_IS_AC3(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;
}
return CONTROL_UNKNOWN;
}
// open & setup audio device
2013-06-07 13:05:20 +00:00
// return: 0=success -1=fail
static int init(struct ao *ao)
2013-06-07 12:29:59 +00:00
{
2013-07-21 21:52:40 +00:00
struct priv *p = ao->priv;
2013-06-07 12:29:59 +00:00
int oss_format;
2013-07-21 21:52:40 +00:00
#ifdef SNDCTL_DSP_GETPLAYVOL
ao->no_persistent_volume = true;
#endif
2013-07-21 21:52:40 +00:00
const char *mchan = NULL;
if (p->cfg_oss_mixer_channel && p->cfg_oss_mixer_channel[0])
mchan = p->cfg_oss_mixer_channel;
2013-06-07 12:29:59 +00:00
if (mchan) {
int fd, devs, i;
2013-06-07 13:20:07 +00:00
if ((fd = open(p->oss_mixer_device, O_RDONLY)) == -1) {
MP_ERR(ao, "Can't open mixer device %s: %s\n",
p->oss_mixer_device, strerror(errno));
2013-06-07 12:29:59 +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);
2013-06-07 12:29:59 +00:00
i = SOUND_MIXER_NRDEVICES + 1;
break;
}
2013-06-07 13:20:07 +00:00
p->oss_mixer_channel = i;
2013-06-07 12:29:59 +00:00
break;
}
}
if (i == SOUND_MIXER_NRDEVICES) {
MP_ERR(ao, "Audio card mixer does not have "
"channel '%s', using default.\n", mchan);
2013-06-07 12:29:59 +00:00
}
}
2013-06-07 12:29:59 +00:00
} else {
2013-06-07 13:20:07 +00:00
p->oss_mixer_channel = SOUND_MIXER_PCM;
}
MP_VERBOSE(ao, "using '%s' dsp device\n", p->dsp);
MP_VERBOSE(ao, "using '%s' mixer device\n", p->oss_mixer_device);
MP_VERBOSE(ao, "using '%s' mixer device\n", mixer_channels[p->oss_mixer_channel]);
#ifdef __linux__
2013-06-07 13:20:07 +00:00
p->audio_fd = open(p->dsp, O_WRONLY | O_NONBLOCK);
#else
2013-06-07 13:20:07 +00:00
p->audio_fd = open(p->dsp, O_WRONLY);
#endif
2013-06-07 13:20:07 +00:00
if (p->audio_fd < 0) {
MP_ERR(ao, "Can't open audio device %s: %s\n", p->dsp, strerror(errno));
2013-06-07 13:05:20 +00:00
return -1;
2013-06-07 12:29:59 +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) {
MP_ERR(ao, "Can't make file descriptor blocking: %s\n", strerror(errno));
2013-06-07 13:05:20 +00:00
return -1;
2013-06-07 12:29:59 +00:00
}
#endif
#if defined(FD_CLOEXEC) && defined(F_SETFD)
2013-06-07 13:20:07 +00:00
fcntl(p->audio_fd, F_SETFD, FD_CLOEXEC);
#endif
ao->format = af_fmt_from_planar(ao->format);
2013-06-07 13:05:20 +00:00
if (AF_FORMAT_IS_AC3(ao->format)) {
2013-06-07 13:20:07 +00:00
ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &ao->samplerate);
2013-06-07 12:29:59 +00:00
}
ac3_retry:
2013-06-07 13:05:20 +00:00
if (AF_FORMAT_IS_AC3(ao->format))
ao->format = AF_FORMAT_AC3;
2013-06-07 13:05:20 +00:00
oss_format = format2oss(ao->format);
2013-06-07 12:29:59 +00:00
if (oss_format == -1) {
MP_VERBOSE(ao, "Unknown/not supported internal format: %s\n",
af_fmt_to_str(ao->format));
#if defined(AFMT_S32_LE) && defined(AFMT_S32_BE)
#if BYTE_ORDER == BIG_ENDIAN
oss_format = AFMT_S32_BE;
#else
oss_format = AFMT_S32_LE;
#endif
ao->format = AF_FORMAT_S32;
#elif defined(AFMT_S24_LE) && defined(AFMT_S24_BE)
#if BYTE_ORDER == BIG_ENDIAN
oss_format = AFMT_S24_BE;
#else
oss_format = AFMT_S24_LE;
#endif
ao->format = AF_FORMAT_S24;
#else
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 15:20:57 +00:00
#if BYTE_ORDER == BIG_ENDIAN
2013-06-07 12:29:59 +00:00
oss_format = AFMT_S16_BE;
#else
2013-06-07 12:29:59 +00:00
oss_format = AFMT_S16_LE;
#endif
ao->format = AF_FORMAT_S16;
#endif
2013-06-07 12:29:59 +00:00
}
2013-06-07 13:20:07 +00:00
if (ioctl(p->audio_fd, SNDCTL_DSP_SETFMT, &oss_format) < 0 ||
2013-06-07 13:05:20 +00:00
oss_format != format2oss(ao->format))
2013-06-07 12:29:59 +00:00
{
MP_WARN(ao, "Can't set audio device %s to %s output, trying %s...\n",
p->dsp, af_fmt_to_str(ao->format),
af_fmt_to_str(AF_FORMAT_S16));
ao->format = AF_FORMAT_S16;
2013-06-07 12:29:59 +00:00
goto ac3_retry;
}
2013-06-07 12:29:59 +00:00
2013-06-07 13:05:20 +00:00
ao->format = oss2format(oss_format);
if (ao->format == -1) {
MP_ERR(ao, "Unknown/Unsupported OSS format: %x.\n", oss_format);
2013-06-07 13:05:20 +00:00
return -1;
}
2013-06-07 12:29:59 +00:00
MP_VERBOSE(ao, "sample format: %s\n", af_fmt_to_str(ao->format));
2013-06-07 12:29:59 +00:00
2013-06-07 13:05:20 +00:00
if (!AF_FORMAT_IS_AC3(ao->format)) {
2013-06-07 12:29:59 +00:00
struct mp_chmap_sel sel = {0};
for (int n = 0; n < MP_NUM_CHANNELS + 1; n++)
mp_chmap_sel_add_map(&sel, &oss_layouts[n]);
2013-06-07 13:05:20 +00:00
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
return -1;
int reqchannels = ao->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)
{
MP_ERR(ao, "Failed to set audio device to %d channels.\n",
reqchannels);
2013-06-07 13:05:20 +00:00
return -1;
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) {
MP_ERR(ao, "Failed to set audio device to %d channels.\n",
reqchannels);
2013-06-07 13:05:20 +00:00
return -1;
2013-06-07 12:29:59 +00:00
}
2013-06-07 13:05:20 +00:00
if (!ao_chmap_sel_get_def(ao, &sel, &ao->channels, c + 1))
return -1;
2013-06-07 12:29:59 +00:00
}
MP_VERBOSE(ao, "using %d channels (requested: %d)\n",
ao->channels.num, reqchannels);
2013-06-07 12:29:59 +00:00
// set rate
2013-06-07 13:20:07 +00:00
ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &ao->samplerate);
MP_VERBOSE(ao, "using %d Hz samplerate\n", ao->samplerate);
2013-06-07 12:29:59 +00:00
}
2013-06-07 13:20:07 +00:00
if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &p->zz) == -1) {
2013-06-07 12:29:59 +00:00
int r = 0;
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)
MP_VERBOSE(ao, "%d bytes/frag (config.h)\n", p->outburst);
2013-06-07 12:29:59 +00:00
else {
p->outburst = r;
MP_VERBOSE(ao, "%d bytes/frag (GETBLKSIZE)\n", p->outburst);
2013-06-07 12:29:59 +00:00
}
} else {
MP_VERBOSE(ao, "frags: %3d/%d (%d bytes/frag) free: %6d\n",
p->zz.fragments, p->zz.fragstotal, p->zz.fragsize, p->zz.bytes);
p->buffersize = p->zz.bytes;
p->outburst = p->zz.fragsize;
}
2013-06-07 12:29:59 +00:00
if (p->buffersize == -1) {
2013-06-07 12:29:59 +00:00
// Measuring buffer size:
void *data;
p->buffersize = 0;
#if HAVE_AUDIO_SELECT
data = malloc(p->outburst);
memset(data, 0, p->outburst);
while (p->buffersize < 0x40000) {
2013-06-07 12:29:59 +00:00
fd_set rfds;
struct timeval tv;
FD_ZERO(&rfds);
2013-06-07 13:20:07 +00:00
FD_SET(p->audio_fd, &rfds);
2013-06-07 12:29:59 +00:00
tv.tv_sec = 0;
tv.tv_usec = 0;
2013-06-07 13:20:07 +00:00
if (!select(p->audio_fd + 1, NULL, &rfds, NULL, &tv))
2013-06-07 12:29:59 +00:00
break;
write(p->audio_fd, data, p->outburst);
p->buffersize += p->outburst;
2013-06-07 12:29:59 +00:00
}
free(data);
if (p->buffersize == 0) {
MP_ERR(ao, "*** Your audio driver DOES NOT support select() ***\n");
MP_ERR(ao, "Recompile mpv with #define HAVE_AUDIO_SELECT 0 in config.h!\n");
2013-06-07 13:05:20 +00:00
return -1;
2013-06-07 12:29:59 +00:00
}
#endif
}
2013-06-07 12:29:59 +00:00
2013-06-07 13:05:20 +00:00
ao->bps = ao->channels.num * (af_fmt2bits(ao->format) / 8);
p->outburst -= p->outburst % ao->bps; // round down
2013-06-07 13:05:20 +00:00
ao->bps *= ao->samplerate;
2013-06-07 13:05:20 +00:00
return 0;
}
// close audio device
static void uninit(struct ao *ao)
2013-06-07 12:29:59 +00:00
{
2013-06-07 13:20:07 +00:00
struct priv *p = ao->priv;
if (p->audio_fd == -1)
2013-06-07 12:29:59 +00:00
return;
#ifdef SNDCTL_DSP_RESET
ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
2013-06-07 13:20:07 +00:00
#endif
close(p->audio_fd);
p->audio_fd = -1;
}
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
}
2013-11-06 19:33:48 +00:00
#ifndef SNDCTL_DSP_RESET
2013-06-07 13:20:07 +00:00
static void close_device(struct ao *ao)
{
struct priv *p = ao->priv;
close(p->audio_fd);
p->audio_fd = -1;
}
2013-11-06 19:33:48 +00:00
#endif
// 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
{
2013-06-07 13:20:07 +00:00
struct priv *p = ao->priv;
2013-06-07 12:29:59 +00:00
int oss_format;
#ifdef SNDCTL_DSP_RESET
ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
#else
2013-06-07 13:20:07 +00:00
close_device(ao);
p->audio_fd = open(p->dsp, O_WRONLY);
if (p->audio_fd < 0) {
MP_ERR(ao, "Fatal error: *** CANNOT "
"RE-OPEN / RESET AUDIO DEVICE *** %s\n", strerror(errno));
2013-06-07 12:29:59 +00:00
return;
}
#if defined(FD_CLOEXEC) && defined(F_SETFD)
2013-06-07 13:20:07 +00:00
fcntl(p->audio_fd, F_SETFD, FD_CLOEXEC);
#endif
2013-06-07 12:29:59 +00:00
#endif
2013-06-07 13:05:20 +00:00
oss_format = format2oss(ao->format);
if (AF_FORMAT_IS_AC3(ao->format))
2013-06-07 13:20:07 +00:00
ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &ao->samplerate);
ioctl(p->audio_fd, SNDCTL_DSP_SETFMT, &oss_format);
2013-06-07 13:05:20 +00:00
if (!AF_FORMAT_IS_AC3(ao->format)) {
int c = ao->channels.num;
2013-06-07 13:05:20 +00:00
if (ao->channels.num > 2)
ioctl(p->audio_fd, SNDCTL_DSP_CHANNELS, &c);
2013-06-07 12:29:59 +00:00
else {
c--;
2013-06-07 13:20:07 +00:00
ioctl(p->audio_fd, SNDCTL_DSP_STEREO, &c);
2013-06-07 12:29:59 +00:00
}
2013-06-07 13:20:07 +00:00
ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &ao->samplerate);
}
}
// return: how many bytes can be played without blocking
2013-06-07 13:05:20 +00:00
static int get_space(struct ao *ao)
2013-06-07 12:29:59 +00:00
{
2013-06-07 13:20:07 +00:00
struct priv *p = ao->priv;
int playsize = p->outburst;
#ifdef SNDCTL_DSP_GETOSPACE
2013-06-07 13:20:07 +00:00
if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &p->zz) != -1) {
2013-06-07 12:29:59 +00:00
// calculate exact buffer space:
2013-06-07 13:20:07 +00:00
playsize = p->zz.fragments * p->zz.fragsize;
return playsize / ao->sstride;
2013-06-07 12:29:59 +00:00
}
#endif
// check buffer
#if HAVE_AUDIO_SELECT
2013-06-07 12:29:59 +00:00
{
fd_set rfds;
struct timeval tv;
FD_ZERO(&rfds);
2013-06-07 13:20:07 +00:00
FD_SET(p->audio_fd, &rfds);
2013-06-07 12:29:59 +00:00
tv.tv_sec = 0;
tv.tv_usec = 0;
2013-06-07 13:20:07 +00:00
if (!select(p->audio_fd + 1, NULL, &rfds, NULL, &tv))
2013-06-07 12:29:59 +00:00
return 0; // not block!
}
#endif
return p->outburst / ao->sstride;
2013-06-07 13:05:20 +00:00
}
// stop playing, keep buffers (for pause)
static void audio_pause(struct ao *ao)
{
2013-06-07 13:20:07 +00:00
struct priv *p = ao->priv;
p->prepause_space = get_space(ao) * ao->sstride;
#ifdef SNDCTL_DSP_RESET
ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
#else
2013-06-07 13:20:07 +00:00
close_device(ao);
#endif
}
// plays 'len' bytes of 'data'
// it should round it down to outburst*n
// return: number of bytes played
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;
int len = samples * ao->sstride;
2013-06-07 12:29:59 +00:00
if (len == 0)
return len;
if (len > p->outburst || !(flags & AOPLAY_FINAL_CHUNK)) {
len /= p->outburst;
len *= p->outburst;
}
len = write(p->audio_fd, data[0], len);
return len / ao->sstride;
}
2013-06-07 13:05:20 +00:00
// resume playing, after audio_pause()
static void audio_resume(struct ao *ao)
{
2013-06-07 13:20:07 +00:00
struct priv *p = ao->priv;
#ifndef SNDCTL_DSP_RESET
2013-06-07 13:05:20 +00:00
reset(ao);
#endif
int fillframes = get_space(ao) - p->prepause_space / ao->sstride;
if (fillframes > 0)
ao_play_silence(ao, fillframes);
2013-06-07 13:05:20 +00:00
}
// return: delay in seconds between first and last sample in buffer
2013-06-07 13:05:20 +00:00
static float 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;
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) {
#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)
2013-06-07 13:05:20 +00:00
return ((float)r) / (float)ao->bps;
#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) {
2013-06-07 12:29:59 +00:00
// SNDCTL_DSP_GETOSPACE
2013-06-07 13:20:07 +00:00
if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &p->zz) != -1) {
return ((float)(p->buffersize -
2013-06-07 13:20:07 +00:00
p->zz.bytes)) / (float)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
}
return ((float)p->buffersize) / (float)ao->bps;
}
2013-06-07 13:05:20 +00:00
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 = {
.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,
.drain = drain,
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,
.dsp = PATH_DEV_DSP,
.oss_mixer_device = PATH_DEV_MIXER,
},
.options = (const struct m_option[]) {
OPT_STRING("device", dsp, 0),
OPT_STRING("mixer-device", oss_mixer_device, 0),
OPT_STRING("mixer-channel", cfg_oss_mixer_channel, 0),
{0}
},
2013-06-07 13:05:20 +00:00
};