diff options
Diffstat (limited to 'audio/out/ao_oss.c')
-rw-r--r-- | audio/out/ao_oss.c | 716 |
1 files changed, 230 insertions, 486 deletions
diff --git a/audio/out/ao_oss.c b/audio/out/ao_oss.c index f037812e70..afe5839dd5 100644 --- a/audio/out/ao_oss.c +++ b/audio/out/ao_oss.c @@ -4,6 +4,7 @@ * Original author: A'rpi * Support for >2 output channels added 2001-11-25 * - Steve Davies <steve@daviesfam.org> + * Rozhuk Ivan <rozhuk.im@gmail.com> 2020-2023 * * This file is part of mpv. * @@ -21,83 +22,53 @@ * with mpv. If not, see <http://www.gnu.org/licenses/>. */ +#include <errno.h> +#include <fcntl.h> #include <stdio.h> -#include <stdlib.h> +#include <unistd.h> #include <sys/ioctl.h> -#include <unistd.h> -#include <sys/time.h> -#include <sys/types.h> +#include <sys/soundcard.h> #include <sys/stat.h> -#include <fcntl.h> -#include <poll.h> -#include <errno.h> -#include <string.h> -#include <strings.h> +#if defined(__DragonFly__) || defined(__FreeBSD__) +#include <sys/sysctl.h> +#endif +#include <sys/types.h> -#include "config.h" -#include "options/options.h" +#include "audio/format.h" #include "common/common.h" #include "common/msg.h" -#include "osdep/timer.h" +#include "options/options.h" #include "osdep/endian.h" - -#include <sys/soundcard.h> - -#include "audio/format.h" - +#include "osdep/io.h" #include "ao.h" #include "internal.h" -#if !HAVE_GPL -#error GPL only -#endif - -// 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. -#if defined(SNDCTL_DSP_RESET) && !defined(__NetBSD__) -#define KEEP_DEVICE 1 -#else -#define KEEP_DEVICE 0 +#ifndef AFMT_AC3 +#define AFMT_AC3 -1 #endif #define PATH_DEV_DSP "/dev/dsp" #define PATH_DEV_MIXER "/dev/mixer" struct priv { - int audio_fd; - int prepause_samples; - int oss_mixer_channel; - int audio_delay_method; - int buffersize; - int outburst; - bool device_failed; - double audio_end; - - char *oss_mixer_device; - char *cfg_oss_mixer_channel; + int dsp_fd; + double bps; /* Bytes per second. */ }; -static const char *const 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 + {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 */ }; -#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) -#endif - #if !defined(AFMT_S32_NE) && defined(AFMT_S32_LE) && defined(AFMT_S32_BE) #define AFMT_S32_NE AFMT_S32MP_SELECT_LE_BE(AFMT_S32_LE, AFMT_S32_BE) #endif @@ -117,541 +88,314 @@ static const int format_table[][2] = { {-1, -1} }; -#ifndef AFMT_AC3 -#define AFMT_AC3 -1 -#endif +#define MP_WARN_IOCTL_ERR(__ao) \ + MP_WARN((__ao), "%s: ioctl() fail, err = %i: %s\n", \ + __FUNCTION__, errno, mp_strerror(errno)) -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 0; -} +static void uninit(struct ao *ao); -#ifdef SNDCTL_DSP_GETPLAYVOL -static int volume_oss4(struct ao *ao, ao_control_vol_t *vol, int cmd) +static void device_descr_get(size_t dev_idx, char *buf, size_t buf_size) { - struct priv *p = ao->priv; - int v; +#if defined(__DragonFly__) || defined(__FreeBSD__) + char dev_path[32]; + size_t tmp = (buf_size - 1); - if (p->audio_fd < 0) - return CONTROL_ERROR; - - if (cmd == AOCONTROL_GET_VOLUME) { - 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; - 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 -static int control(struct ao *ao, enum aocontrol cmd, void *arg) -{ - struct priv *p = ao->priv; - 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 - - if (!af_fmt_is_pcm(ao->format)) - return CONTROL_TRUE; - - if ((fd = open(p->oss_mixer_device, O_RDONLY)) != -1) { - ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devs); - if (devs & (1 << p->oss_mixer_channel)) { - if (cmd == AOCONTROL_GET_VOLUME) { - ioctl(fd, MIXER_READ(p->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(p->oss_mixer_channel), &v); - } - } else { - close(fd); - return CONTROL_ERROR; - } - close(fd); - return CONTROL_OK; - } - return CONTROL_ERROR; + snprintf(dev_path, sizeof(dev_path), "dev.pcm.%zu.%%desc", dev_idx); + if (sysctlbyname(dev_path, buf, &tmp, NULL, 0) != 0) { + tmp = 0; } -#ifdef SNDCTL_DSP_GETPLAYVOL - case AOCONTROL_HAS_SOFT_VOLUME: - return CONTROL_TRUE; -#endif + buf[tmp] = 0x00; +#elif defined(SOUND_MIXER_INFO) + size_t tmp = 0; + char dev_path[32]; + mixer_info mi; + + snprintf(dev_path, sizeof(dev_path), PATH_DEV_MIXER"%zu", dev_idx); + int fd = open(dev_path, O_RDONLY); + if (ioctl(fd, SOUND_MIXER_INFO, &mi) == 0) { + strncpy(buf, mi.name, buf_size - 1); + tmp = (buf_size - 1); } - return CONTROL_UNKNOWN; -} - -// 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); -} - -static void close_device(struct ao *ao) -{ - struct priv *p = ao->priv; - p->device_failed = false; - if (p->audio_fd == -1) - return; -#if defined(SNDCTL_DSP_RESET) - ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL); + close(fd); + buf[tmp] = 0x00; +#else + buf[0] = 0x00; #endif - close(p->audio_fd); - p->audio_fd = -1; } -// close audio device -static void uninit(struct ao *ao) +static int format2oss(int format) { - close_device(ao); + for (size_t i = 0; format_table[i][0] != -1; i++) { + if (format_table[i][1] == format) + return format_table[i][0]; + } + return -1; } static bool try_format(struct ao *ao, int *format) { struct priv *p = ao->priv; - int oss_format = format2oss(*format); + if (oss_format == -1 && af_fmt_is_spdif(*format)) oss_format = AFMT_AC3; if (oss_format == -1) { MP_VERBOSE(ao, "Unknown/not supported internal format: %s\n", - af_fmt_to_str(*format)); + 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; + return (ioctl(p->dsp_fd, SNDCTL_DSP_SETFMT, &oss_format) != -1); } -static int reopen_device(struct ao *ao, bool allow_format_changes) +static int init(struct ao *ao) { struct priv *p = ao->priv; - - int samplerate = ao->samplerate; - int format = ao->format; struct mp_chmap channels = ao->channels; - - const char *device = PATH_DEV_DSP; - if (ao->device) - device = ao->device; - - MP_VERBOSE(ao, "using '%s' dsp device\n", device); -#ifdef __linux__ - p->audio_fd = open(device, O_WRONLY | O_NONBLOCK); -#else - p->audio_fd = open(device, O_WRONLY); -#endif - if (p->audio_fd < 0) { - MP_ERR(ao, "Can't open audio device %s: %s\n", - device, mp_strerror(errno)); - goto fail; + audio_buf_info info; + size_t i; + int format, samplerate, nchannels, reqchannels, trig = 0; + int best_sample_formats[AF_FORMAT_COUNT + 1]; + const char *device = ((ao->device) ? ao->device : PATH_DEV_DSP); + + /* Opening device. */ + MP_VERBOSE(ao, "Using '%s' audio device.\n", device); + p->dsp_fd = open(device, (O_WRONLY | O_CLOEXEC)); + if (p->dsp_fd < 0) { + MP_ERR(ao, "Can't open audio device %s: %s.\n", + device, mp_strerror(errno)); + goto err_out; } -#ifdef __linux__ - /* Remove the non-blocking flag */ - if (fcntl(p->audio_fd, F_SETFL, 0) < 0) { - MP_ERR(ao, "Can't make file descriptor blocking: %s\n", - mp_strerror(errno)); - goto fail; - } -#endif - -#if defined(FD_CLOEXEC) && defined(F_SETFD) - fcntl(p->audio_fd, F_SETFD, FD_CLOEXEC); -#endif - - if (af_fmt_is_spdif(format)) { - if (ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &samplerate) == -1) - goto fail; - // 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; - } - } - - int try_formats[AF_FORMAT_COUNT + 1]; - af_get_best_sample_formats(format, try_formats); - for (int n = 0; try_formats[n]; n++) { - format = try_formats[n]; + /* Selecting sound format. */ + format = af_fmt_from_planar(ao->format); + af_get_best_sample_formats(format, best_sample_formats); + for (i = 0; best_sample_formats[i]; i++) { + format = best_sample_formats[i]; if (try_format(ao, &format)) break; } - if (!format) { MP_ERR(ao, "Can't set sample format.\n"); - goto fail; + goto err_out; } + MP_VERBOSE(ao, "Sample format: %s\n", af_fmt_to_str(format)); - MP_VERBOSE(ao, "sample format: %s\n", af_fmt_to_str(format)); - - if (!af_fmt_is_spdif(format)) { + /* Channels count. */ + if (af_fmt_is_spdif(format)) { + nchannels = reqchannels = channels.num; + if (ioctl(p->dsp_fd, SNDCTL_DSP_CHANNELS, &nchannels) == -1) { + MP_ERR(ao, "Failed to set audio device to %d channels.\n", + reqchannels); + goto err_out_ioctl; + } + } else { 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]); + for (i = 0; i < MP_ARRAY_SIZE(oss_layouts); i++) { + mp_chmap_sel_add_map(&sel, &oss_layouts[i]); + } if (!ao_chmap_sel_adjust(ao, &sel, &channels)) - goto fail; - int reqchannels = channels.num; - // 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(p->audio_fd, SNDCTL_DSP_CHANNELS, &nchannels) == -1 || - nchannels != reqchannels) - { - MP_ERR(ao, "Failed to set audio device to %d channels.\n", - reqchannels); - goto fail; - } - } else { - int c = reqchannels - 1; - if (ioctl(p->audio_fd, SNDCTL_DSP_STEREO, &c) == -1) { - MP_ERR(ao, "Failed to set audio device to %d channels.\n", - reqchannels); - goto fail; - } - if (!ao_chmap_sel_get_def(ao, &sel, &channels, c + 1)) - goto fail; + goto err_out; + nchannels = reqchannels = channels.num; + if (ioctl(p->dsp_fd, SNDCTL_DSP_CHANNELS, &nchannels) == -1) { + MP_ERR(ao, "Failed to set audio device to %d channels.\n", + reqchannels); + goto err_out_ioctl; } - MP_VERBOSE(ao, "using %d channels (requested: %d)\n", - channels.num, reqchannels); - // set rate - if (ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &samplerate) == -1) - goto fail; - MP_VERBOSE(ao, "using %d Hz samplerate\n", samplerate); - } - - audio_buf_info zz = {0}; - if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &zz) == -1) { - int r = 0; - MP_WARN(ao, "driver doesn't support SNDCTL_DSP_GETOSPACE\n"); - if (ioctl(p->audio_fd, SNDCTL_DSP_GETBLKSIZE, &r) == -1) - MP_VERBOSE(ao, "%d bytes/frag (config.h)\n", p->outburst); - else { - p->outburst = r; - MP_VERBOSE(ao, "%d bytes/frag (GETBLKSIZE)\n", p->outburst); + if (nchannels != reqchannels) { + /* Update number of channels to OSS suggested value. */ + if (!ao_chmap_sel_get_def(ao, &sel, &channels, nchannels)) + goto err_out; } - } else { - MP_VERBOSE(ao, "frags: %3d/%d (%d bytes/frag) free: %6d\n", - zz.fragments, zz.fragstotal, zz.fragsize, zz.bytes); - p->buffersize = zz.bytes; - p->outburst = zz.fragsize; + MP_VERBOSE(ao, "Using %d channels (requested: %d).\n", + channels.num, reqchannels); } - 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; - } + /* Sample rate. */ + samplerate = ao->samplerate; + if (ioctl(p->dsp_fd, SNDCTL_DSP_SPEED, &samplerate) == -1) + goto err_out_ioctl; + MP_VERBOSE(ao, "Using %d Hz samplerate.\n", samplerate); + + /* Get buffer size. */ + if (ioctl(p->dsp_fd, SNDCTL_DSP_GETOSPACE, &info) == -1) + goto err_out_ioctl; + /* See ao.c ao->sstride initializations and get_state(). */ + ao->device_buffer = ((info.fragstotal * info.fragsize) / + af_fmt_to_bytes(format)); + if (!af_fmt_is_planar(format)) { + ao->device_buffer /= channels.num; } - int sstride = channels.num * af_fmt_to_bytes(format); - p->outburst -= p->outburst % sstride; // round down - ao->period_size = p->outburst / sstride; + /* Do not start playback after data written. */ + if (ioctl(p->dsp_fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) + goto err_out_ioctl; + + /* Update sound params. */ + ao->format = format; + ao->samplerate = samplerate; + ao->channels = channels; + p->bps = (channels.num * samplerate * af_fmt_to_bytes(format)); return 0; -fail: - close_device(ao); +err_out_ioctl: + MP_WARN_IOCTL_ERR(ao); +err_out: + uninit(ao); return -1; } -// open & setup audio device -// return: 0=success -1=fail -static int init(struct ao *ao) +static void uninit(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", - p->oss_mixer_device, mp_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_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); - MP_VERBOSE(ao, "using '%s' mixer channel\n", mixer_channels[p->oss_mixer_channel]); + if (p->dsp_fd == -1) + return; + ioctl(p->dsp_fd, SNDCTL_DSP_HALT, NULL); + close(p->dsp_fd); + p->dsp_fd = -1; +} - ao->format = af_fmt_from_planar(ao->format); +static int control(struct ao *ao, enum aocontrol cmd, void *arg) +{ + struct priv *p = ao->priv; + float *vol = arg; + int v; - if (reopen_device(ao, true) < 0) - goto fail; + if (p->dsp_fd < 0) + return CONTROL_ERROR; - if (p->buffersize == -1) { - // Measuring buffer size: - void *data = malloc(p->outburst); - if (!data) { - MP_ERR(ao, "Out of memory, or broken outburst size.\n"); - goto fail; - } - p->buffersize = 0; - memset(data, 0, p->outburst); - while (p->buffersize < 0x40000 && device_writable(ao) > 0) { - (void)write(p->audio_fd, data, p->outburst); - p->buffersize += p->outburst; + switch (cmd) { + case AOCONTROL_GET_VOLUME: + if (ioctl(p->dsp_fd, SNDCTL_DSP_GETPLAYVOL, &v) == -1) { + MP_WARN_IOCTL_ERR(ao); + return CONTROL_ERROR; } - free(data); - if (p->buffersize == 0) { - MP_ERR(ao, "Your OSS audio driver DOES NOT support poll().\n"); - goto fail; + *vol = ((v & 0x00ff) + ((v & 0xff00) >> 8)) / 2.0; + return CONTROL_OK; + case AOCONTROL_SET_VOLUME: + v = ((int)*vol << 8) | (int)*vol; + if (ioctl(p->dsp_fd, SNDCTL_DSP_SETPLAYVOL, &v) == -1) { + MP_WARN_IOCTL_ERR(ao); + return CONTROL_ERROR; } + return CONTROL_OK; } - return 0; - -fail: - uninit(ao); - return -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 + return CONTROL_UNKNOWN; } -// stop playing and empty buffers (for seeking/pause) static void reset(struct ao *ao) { -#if KEEP_DEVICE struct priv *p = ao->priv; - ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL); -#else - close_device(ao); -#endif + int trig = 0; + + /* Clear buf and do not start playback after data written. */ + if (ioctl(p->dsp_fd, SNDCTL_DSP_HALT, NULL) == -1 || + ioctl(p->dsp_fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) + { + MP_WARN_IOCTL_ERR(ao); + MP_WARN(ao, "Force reinitialize audio device.\n"); + uninit(ao); + init(ao); + } } -// plays 'len' samples of 'data' -// it should round it down to outburst*n -// return: number of samples played -static int play(struct ao *ao, void **data, int samples, int flags) +static void start(struct ao *ao) { struct priv *p = ao->priv; - int len = samples * ao->sstride; - if (len == 0) - return len; - - 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; - } + int trig = PCM_ENABLE_OUTPUT; - if (len > p->outburst || !(flags & AOPLAY_FINAL_CHUNK)) { - len /= p->outburst; - len *= p->outburst; + if (ioctl(p->dsp_fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) { + MP_WARN_IOCTL_ERR(ao); + return; } - len = write(p->audio_fd, data[0], len); - return len / ao->sstride; } -// return: delay in seconds between first and last sample in buffer -static double get_delay(struct ao *ao) +static bool audio_write(struct ao *ao, void **data, int samples) { struct priv *p = ao->priv; - if (p->audio_fd < 0) { - double rest = p->audio_end - mp_time_sec(); - if (rest > 0) - return rest; - return 0; - } - /* Calculate how many bytes/second is sent out */ - if (p->audio_delay_method == 2) { -#ifdef SNDCTL_DSP_GETODELAY - int r = 0; - if (ioctl(p->audio_fd, SNDCTL_DSP_GETODELAY, &r) != -1) - return r / (double)ao->bps; -#endif - p->audio_delay_method = 1; // fallback if not supported - } - if (p->audio_delay_method == 1) { - audio_buf_info zz = {0}; - if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &zz) != -1) { - return (p->buffersize - zz.bytes) / (double)ao->bps; - } - p->audio_delay_method = 0; // fallback if not supported - } - return p->buffersize / (double)ao->bps; -} - + ssize_t rc; + const size_t size = (samples * ao->sstride); -// return: how many samples can be played without blocking -static int get_space(struct ao *ao) -{ - struct priv *p = ao->priv; + if (size == 0) + return true; - 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; + while ((rc = write(p->dsp_fd, data[0], size)) == -1) { + if (errno == EINTR) + continue; + MP_WARN(ao, "audio_write: write() fail, err = %i: %s.\n", + errno, mp_strerror(errno)); + return false; + } + if ((size_t)rc != size) { + MP_WARN(ao, "audio_write: unexpected partial write: required: %zu, written: %zu.\n", + size, (size_t)rc); + return false; } - if (p->audio_fd < 0 && p->device_failed && get_delay(ao) > 0.2) - return 0; - - if (p->audio_fd < 0 || device_writable(ao) > 0) - return p->outburst / ao->sstride; - - return 0; + return true; } -// stop playing, keep buffers (for pause) -static void audio_pause(struct ao *ao) +static void get_state(struct ao *ao, struct mp_pcm_state *state) { 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 -} - -// 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); + audio_buf_info info; + int odelay; + + if (ioctl(p->dsp_fd, SNDCTL_DSP_GETOSPACE, &info) == -1 || + ioctl(p->dsp_fd, SNDCTL_DSP_GETODELAY, &odelay) == -1) + { + MP_WARN_IOCTL_ERR(ao); + memset(state, 0x00, sizeof(struct mp_pcm_state)); + state->delay = 0.0; + return; + } + state->free_samples = (info.bytes / ao->sstride); + state->queued_samples = (ao->device_buffer - state->free_samples); + state->delay = (odelay / p->bps); + state->playing = (state->queued_samples != 0); } -static int audio_wait(struct ao *ao, pthread_mutex_t *lock) +static void list_devs(struct ao *ao, struct ao_device_list *list) { - struct priv *p = ao->priv; + struct stat st; + char dev_path[32] = PATH_DEV_DSP, dev_descr[256] = "Default"; + struct ao_device_desc dev = {.name = dev_path, .desc = dev_descr}; - 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; -} + if (stat(PATH_DEV_DSP, &st) == 0) { + ao_device_list_add(list, ao, &dev); + } -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"}); + /* Auto detect. */ + for (size_t i = 0, fail_cnt = 0; fail_cnt < 8; i ++, fail_cnt ++) { + snprintf(dev_path, sizeof(dev_path), PATH_DEV_DSP"%zu", i); + if (stat(dev_path, &st) != 0) + continue; + device_descr_get(i, dev_descr, sizeof(dev_descr)); + ao_device_list_add(list, ao, &dev); + fail_cnt = 0; /* Reset fail counter. */ + } } -#define OPT_BASE_STRUCT struct priv - const struct ao_driver audio_out_oss = { - .description = "OSS/ioctl audio output", .name = "oss", + .description = "OSS/ioctl audio output", .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, - .wait = audio_wait, - .wakeup = ao_wakeup_poll, + .start = start, + .write = audio_write, + .get_state = get_state, .list_devs = list_devs, .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} + .dsp_fd = -1, }, - .options_prefix = "oss", }; |