From 1b2e5137e04bf4990713714b4925fbdd30a9d6c9 Mon Sep 17 00:00:00 2001 From: rim Date: Tue, 24 Nov 2020 04:22:40 +0300 Subject: ao_oss: add this audio output again Changes: - code refactored; - mixer options removed; - new mpv sound API used; - add sound devices detect (mpv --audio-device=help will show all available devices); - only OSSv4 supported now; Tested on FreeBSD 12.2 amd64. --- audio/out/ao_oss.c | 410 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 410 insertions(+) create mode 100644 audio/out/ao_oss.c (limited to 'audio/out/ao_oss.c') diff --git a/audio/out/ao_oss.c b/audio/out/ao_oss.c new file mode 100644 index 0000000000..11b182e52d --- /dev/null +++ b/audio/out/ao_oss.c @@ -0,0 +1,410 @@ +/* + * OSS audio output driver + * + * Original author: A'rpi + * Support for >2 output channels added 2001-11-25 + * - Steve Davies + * Rozhuk Ivan 2020 + * + * This file is part of mpv. + * + * mpv 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. + * + * mpv 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 mpv. If not, see . + */ + +#include +#include +#include +#include + +#include +#include +#include +#if defined(__DragonFly__) || defined(__FreeBSD__) +#include +#endif +#include + +#include "config.h" +#include "audio/format.h" +#include "common/msg.h" +#include "options/options.h" +#include "osdep/endian.h" +#include "osdep/io.h" +#include "ao.h" +#include "internal.h" + +#ifndef AFMT_AC3 +#define AFMT_AC3 -1 +#endif + +#define PATH_DEV_DSP "/dev/dsp" + +struct priv { + int dsp_fd; + bool playing; + double bps; /* Bytes per second. */ +}; + +/* 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 */ +}; + +#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 + +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}, +#endif +#ifdef AFMT_FLOAT + {AFMT_FLOAT, AF_FORMAT_FLOAT}, +#endif +#ifdef AFMT_MPEG + {AFMT_MPEG, AF_FORMAT_S_MP3}, +#endif + {-1, -1} +}; + +#define MP_WARN_IOCTL_ERR(__ao) \ + MP_WARN((__ao), "%s: ioctl() fail, err = %i: %s\n", \ + __FUNCTION__, errno, strerror(errno)) + + +static void uninit(struct ao *ao); + + +static void device_descr_get(size_t dev_idx, char *buf, size_t buf_size) +{ +#if defined(__DragonFly__) || defined(__FreeBSD__) + char dev_path[32]; + size_t tmp = (buf_size - 1); + + snprintf(dev_path, sizeof(dev_path), "dev.pcm.%zu.%%desc", dev_idx); + if (sysctlbyname(dev_path, buf, &tmp, NULL, 0) != 0) { + tmp = 0; + } + 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); + tmp = (buf_size - 1); + } + close(fd); + buf[tmp] = 0x00; +#else + buf[0] = 0x00; +#endif +} + +static int format2oss(int format) +{ + 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)); + *format = 0; + return false; + } + + return (ioctl(p->dsp_fd, SNDCTL_DSP_SETFMT, &oss_format) != -1); +} + +static int init(struct ao *ao) +{ + struct priv *p = ao->priv; + struct mp_chmap channels = ao->channels; + 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; + } + + /* 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 err_out; + } + MP_VERBOSE(ao, "Sample format: %s\n", af_fmt_to_str(format)); + + /* Channels count. */ + if (af_fmt_is_spdif(format)) { + /* 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 err_out; + } + } else { + struct mp_chmap_sel sel = {0}; + 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 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; + } + if (nchannels != reqchannels) { + /* Update number of channels to OSS suggested value. */ + if (!ao_chmap_sel_get_def(ao, &sel, &channels, nchannels)) + goto err_out; + } + MP_VERBOSE(ao, "Using %d channels (requested: %d).\n", + channels.num, reqchannels); + } + + /* 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; + } + + /* 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)); + p->playing = false; + + return 0; + +err_out_ioctl: + MP_WARN_IOCTL_ERR(ao); +err_out: + uninit(ao); + return -1; +} + +static void uninit(struct ao *ao) +{ + struct priv *p = ao->priv; + + if (p->dsp_fd == -1) + return; + ioctl(p->dsp_fd, SNDCTL_DSP_HALT, NULL); + close(p->dsp_fd); + p->dsp_fd = -1; + p->playing = false; +} + +static int control(struct ao *ao, enum aocontrol cmd, void *arg) +{ + struct priv *p = ao->priv; + ao_control_vol_t *vol = (ao_control_vol_t *)arg; + int v; + + if (p->dsp_fd < 0) + return CONTROL_ERROR; + + switch (cmd) { + case AOCONTROL_GET_VOLUME: + if (ioctl(p->dsp_fd, SNDCTL_DSP_GETPLAYVOL, &v) == -1) { + MP_WARN_IOCTL_ERR(ao); + return CONTROL_ERROR; + } + vol->right = ((v & 0xff00) >> 8); + vol->left = (v & 0x00ff); + return CONTROL_OK; + case AOCONTROL_SET_VOLUME: + v = ((int)vol->right << 8) | (int)vol->left; + if (ioctl(p->dsp_fd, SNDCTL_DSP_SETPLAYVOL, &v) == -1) { + MP_WARN_IOCTL_ERR(ao); + return CONTROL_ERROR; + } + return CONTROL_OK; + } + + return CONTROL_UNKNOWN; +} + +static void reset(struct ao *ao) +{ + struct priv *p = ao->priv; + int trig = 0; + + /* Clear buf and do not start playback after data written. */ + p->playing = false; + 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); + } +} + +static void start(struct ao *ao) +{ + struct priv *p = ao->priv; + int trig = PCM_ENABLE_OUTPUT; + + if (ioctl(p->dsp_fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) { + MP_WARN_IOCTL_ERR(ao); + return; + } + p->playing = true; +} + +static bool audio_write(struct ao *ao, void **data, int samples) +{ + struct priv *p = ao->priv; + ssize_t rc; + const size_t size = (samples * ao->sstride); + + if (size == 0) + return true; + + 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, strerror(errno)); + p->playing = false; + return false; + } + if ((size_t)rc != size) { + MP_WARN(ao, "audio_write: unexpected partial write: required: %zu, written: %zu.\n", + size, (size_t)rc); + p->playing = false; + return false; + } + + return true; +} + +static void get_state(struct ao *ao, struct mp_pcm_state *state) +{ + struct priv *p = ao->priv; + 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); + p->playing = false; + 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 = p->playing; +} + +static void list_devs(struct ao *ao, struct ao_device_list *list) +{ + 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}; + + if (stat(PATH_DEV_DSP, &st) == 0) { + ao_device_list_add(list, ao, &dev); + } + + /* 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. */ + } +} + +const struct ao_driver audio_out_oss = { + .name = "oss", + .description = "OSS/ioctl audio output", + .init = init, + .uninit = uninit, + .control = control, + .reset = reset, + .start = start, + .write = audio_write, + .get_state = get_state, + .list_devs = list_devs, + .priv_size = sizeof(struct priv), + .priv_defaults = &(const struct priv) { + .dsp_fd = -1, + .playing = false, + }, +}; -- cgit v1.2.3