2020-11-25 03:10:19 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
|
|
|
|
* Copyright (c) 2013 Christian Neukirchen <chneukirchen@gmail.com>
|
|
|
|
* Copyright (c) 2020 Rozhuk Ivan <rozhuk.im@gmail.com>
|
2022-01-11 16:23:52 +00:00
|
|
|
* Copyright (c) 2021 Andrew Krasavin <noiseless-ak@yandex.ru>
|
2022-05-12 15:12:31 +00:00
|
|
|
*
|
2020-11-25 03:10:19 +00:00
|
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <poll.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <sndio.h>
|
|
|
|
|
2024-02-06 18:33:22 +00:00
|
|
|
#include "config.h"
|
|
|
|
|
2020-11-25 03:10:19 +00:00
|
|
|
#include "options/m_option.h"
|
|
|
|
#include "common/msg.h"
|
|
|
|
|
|
|
|
#include "audio/format.h"
|
|
|
|
#include "ao.h"
|
|
|
|
#include "internal.h"
|
|
|
|
|
|
|
|
struct priv {
|
|
|
|
struct sio_hdl *hdl;
|
|
|
|
struct sio_par par;
|
|
|
|
int delay;
|
|
|
|
bool playing;
|
|
|
|
int vol;
|
|
|
|
int havevol;
|
|
|
|
struct pollfd *pfd;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2022-01-11 16:23:52 +00:00
|
|
|
static const struct mp_chmap sndio_layouts[] = {
|
2020-11-25 03:10:19 +00:00
|
|
|
{0}, /* empty */
|
|
|
|
{1, {MP_SPEAKER_ID_FL}}, /* mono */
|
|
|
|
MP_CHMAP2(FL, FR), /* stereo */
|
|
|
|
{0}, /* 2.1 */
|
|
|
|
MP_CHMAP4(FL, FR, BL, BR), /* 4.0 */
|
|
|
|
{0}, /* 5.0 */
|
|
|
|
MP_CHMAP6(FL, FR, BL, BR, FC, LFE), /* 5.1 */
|
|
|
|
{0}, /* 6.1 */
|
|
|
|
MP_CHMAP8(FL, FR, BL, BR, FC, LFE, SL, SR), /* 7.1 */
|
|
|
|
/* Above is the fixed channel assignment for sndio, since we need to
|
|
|
|
* fill all channels and cannot insert silence, not all layouts are
|
|
|
|
* supported.
|
|
|
|
* NOTE: MP_SPEAKER_ID_NA could be used to add padding channels. */
|
|
|
|
};
|
|
|
|
|
|
|
|
static void uninit(struct ao *ao);
|
|
|
|
|
|
|
|
|
|
|
|
/* Make libsndio call movecb(). */
|
|
|
|
static void process_events(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
2022-01-11 16:23:52 +00:00
|
|
|
|
2020-11-25 03:10:19 +00:00
|
|
|
int n = sio_pollfd(p->hdl, p->pfd, POLLOUT);
|
2022-01-11 16:23:52 +00:00
|
|
|
while (poll(p->pfd, n, 0) < 0 && errno == EINTR);
|
2020-11-25 03:10:19 +00:00
|
|
|
|
|
|
|
sio_revents(p->hdl, p->pfd);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Call-back invoked to notify of the hardware position. */
|
|
|
|
static void movecb(void *addr, int delta)
|
|
|
|
{
|
|
|
|
struct ao *ao = addr;
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
|
|
|
p->delay -= delta;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Call-back invoked to notify about volume changes. */
|
|
|
|
static void volcb(void *addr, unsigned newvol)
|
|
|
|
{
|
|
|
|
struct ao *ao = addr;
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
|
|
|
p->vol = newvol;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int init(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
struct mp_chmap_sel sel = {0};
|
|
|
|
size_t i;
|
|
|
|
struct af_to_par {
|
|
|
|
int format, bits, sig;
|
|
|
|
};
|
|
|
|
static const struct af_to_par af_to_par[] = {
|
|
|
|
{AF_FORMAT_U8, 8, 0},
|
|
|
|
{AF_FORMAT_S16, 16, 1},
|
|
|
|
{AF_FORMAT_S32, 32, 1},
|
|
|
|
};
|
|
|
|
const struct af_to_par *ap;
|
|
|
|
const char *device = ((ao->device) ? ao->device : SIO_DEVANY);
|
|
|
|
|
|
|
|
/* Opening device. */
|
|
|
|
MP_VERBOSE(ao, "Using '%s' audio device.\n", device);
|
|
|
|
p->hdl = sio_open(device, SIO_PLAY, 0);
|
|
|
|
if (p->hdl == NULL) {
|
|
|
|
MP_ERR(ao, "Can't open audio device %s.\n", device);
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
|
|
|
|
sio_initpar(&p->par);
|
|
|
|
|
|
|
|
/* Selecting sound format. */
|
|
|
|
ao->format = af_fmt_from_planar(ao->format);
|
2022-01-11 16:23:52 +00:00
|
|
|
|
|
|
|
p->par.bits = 16;
|
|
|
|
p->par.sig = 1;
|
|
|
|
p->par.le = SIO_LE_NATIVE;
|
|
|
|
for (i = 0; i < MP_ARRAY_SIZE(af_to_par); i++) {
|
|
|
|
ap = &af_to_par[i];
|
2020-11-25 03:10:19 +00:00
|
|
|
if (ap->format == ao->format) {
|
|
|
|
p->par.bits = ap->bits;
|
|
|
|
p->par.sig = ap->sig;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
p->par.rate = ao->samplerate;
|
|
|
|
|
|
|
|
/* Channels count. */
|
|
|
|
for (i = 0; i < MP_ARRAY_SIZE(sndio_layouts); i++) {
|
|
|
|
mp_chmap_sel_add_map(&sel, &sndio_layouts[i]);
|
|
|
|
}
|
|
|
|
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
|
|
|
goto err_out;
|
|
|
|
|
|
|
|
p->par.pchan = ao->channels.num;
|
2022-01-11 16:23:52 +00:00
|
|
|
p->par.appbufsz = p->par.rate * 250 / 1000; /* 250ms buffer */
|
|
|
|
p->par.round = p->par.rate * 10 / 1000; /* 10ms block size */
|
|
|
|
|
2020-11-25 03:10:19 +00:00
|
|
|
if (!sio_setpar(p->hdl, &p->par)) {
|
|
|
|
MP_ERR(ao, "couldn't set params\n");
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get current sound params. */
|
|
|
|
if (!sio_getpar(p->hdl, &p->par)) {
|
|
|
|
MP_ERR(ao, "couldn't get params\n");
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
if (p->par.bps > 1 && p->par.le != SIO_LE_NATIVE) {
|
|
|
|
MP_ERR(ao, "swapped endian output not supported\n");
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Update sound params. */
|
|
|
|
if (p->par.bits == 8 && p->par.bps == 1 && !p->par.sig) {
|
|
|
|
ao->format = AF_FORMAT_U8;
|
|
|
|
} else if (p->par.bits == 16 && p->par.bps == 2 && p->par.sig) {
|
|
|
|
ao->format = AF_FORMAT_S16;
|
|
|
|
} else if ((p->par.bits == 32 || p->par.msb) && p->par.bps == 4 && p->par.sig) {
|
|
|
|
ao->format = AF_FORMAT_S32;
|
|
|
|
} else {
|
|
|
|
MP_ERR(ao, "couldn't set format\n");
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
|
|
|
|
p->havevol = sio_onvol(p->hdl, volcb, ao);
|
|
|
|
sio_onmove(p->hdl, movecb, ao);
|
|
|
|
|
2022-01-11 16:23:52 +00:00
|
|
|
p->pfd = talloc_array_ptrtype(p, p->pfd, sio_nfds(p->hdl));
|
2020-11-25 03:10:19 +00:00
|
|
|
if (!p->pfd)
|
|
|
|
goto err_out;
|
|
|
|
|
|
|
|
ao->device_buffer = p->par.bufsz;
|
|
|
|
MP_VERBOSE(ao, "bufsz = %i, appbufsz = %i, round = %i\n",
|
|
|
|
p->par.bufsz, p->par.appbufsz, p->par.round);
|
|
|
|
|
|
|
|
p->delay = 0;
|
|
|
|
p->playing = false;
|
|
|
|
if (!sio_start(p->hdl)) {
|
|
|
|
MP_ERR(ao, "start: sio_start() fail.\n");
|
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_out:
|
|
|
|
uninit(ao);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void uninit(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
|
|
|
if (p->hdl) {
|
|
|
|
sio_close(p->hdl);
|
|
|
|
p->hdl = NULL;
|
|
|
|
}
|
|
|
|
p->pfd = NULL;
|
|
|
|
p->playing = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
2022-12-31 03:46:44 +00:00
|
|
|
float *vol = arg;
|
2020-11-25 03:10:19 +00:00
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case AOCONTROL_GET_VOLUME:
|
|
|
|
if (!p->havevol)
|
|
|
|
return CONTROL_FALSE;
|
2022-12-31 03:46:44 +00:00
|
|
|
*vol = p->vol * 100 / SIO_MAXVOL;
|
2020-11-25 03:10:19 +00:00
|
|
|
break;
|
|
|
|
case AOCONTROL_SET_VOLUME:
|
|
|
|
if (!p->havevol)
|
|
|
|
return CONTROL_FALSE;
|
2022-12-31 03:46:44 +00:00
|
|
|
sio_setvol(p->hdl, *vol * SIO_MAXVOL / 100);
|
2020-11-25 03:10:19 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return CONTROL_UNKNOWN;
|
|
|
|
}
|
|
|
|
return CONTROL_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void reset(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
2022-01-11 16:23:52 +00:00
|
|
|
if (p->playing) {
|
|
|
|
p->playing = false;
|
2020-11-25 03:10:19 +00:00
|
|
|
|
2023-07-24 20:01:25 +00:00
|
|
|
#if HAVE_SNDIO_1_9
|
|
|
|
if (!sio_flush(p->hdl)) {
|
|
|
|
MP_ERR(ao, "reset: couldn't sio_flush()\n");
|
|
|
|
#else
|
2022-01-11 16:23:52 +00:00
|
|
|
if (!sio_stop(p->hdl)) {
|
|
|
|
MP_ERR(ao, "reset: couldn't sio_stop()\n");
|
2023-07-24 20:01:25 +00:00
|
|
|
#endif
|
2022-01-11 16:23:52 +00:00
|
|
|
}
|
|
|
|
p->delay = 0;
|
|
|
|
if (!sio_start(p->hdl)) {
|
|
|
|
MP_ERR(ao, "reset: sio_start() fail.\n");
|
|
|
|
}
|
2020-11-25 03:10:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void start(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
|
|
|
p->playing = true;
|
|
|
|
process_events(ao);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool audio_write(struct ao *ao, void **data, int samples)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
const size_t size = (samples * ao->sstride);
|
|
|
|
size_t rc;
|
|
|
|
|
|
|
|
rc = sio_write(p->hdl, data[0], size);
|
|
|
|
if (rc != size) {
|
|
|
|
MP_WARN(ao, "audio_write: unexpected partial write: required: %zu, written: %zu.\n",
|
|
|
|
size, rc);
|
|
|
|
reset(ao);
|
|
|
|
p->playing = false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
p->delay += samples;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void get_state(struct ao *ao, struct mp_pcm_state *state)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
|
|
|
process_events(ao);
|
|
|
|
|
2022-01-11 16:23:52 +00:00
|
|
|
/* how many samples we can play without blocking */
|
|
|
|
state->free_samples = ao->device_buffer - p->delay;
|
|
|
|
state->free_samples = state->free_samples / p->par.round * p->par.round;
|
|
|
|
/* how many samples are already in the buffer to be played */
|
2020-11-25 03:10:19 +00:00
|
|
|
state->queued_samples = p->delay;
|
2022-01-11 16:23:52 +00:00
|
|
|
/* delay in seconds between first and last sample in buffer */
|
|
|
|
state->delay = p->delay / (double)p->par.rate;
|
|
|
|
|
|
|
|
/* report unexpected EOF / underrun */
|
2023-11-24 01:08:33 +00:00
|
|
|
if ((state->queued_samples &&
|
2022-03-06 17:46:52 +00:00
|
|
|
(state->queued_samples < state->free_samples) &&
|
|
|
|
p->playing) || sio_eof(p->hdl))
|
2022-01-11 16:23:52 +00:00
|
|
|
{
|
|
|
|
MP_VERBOSE(ao, "get_state: EOF/underrun detected.\n");
|
|
|
|
MP_VERBOSE(ao, "get_state: free: %d, queued: %d, delay: %lf\n", \
|
|
|
|
state->free_samples, state->queued_samples, state->delay);
|
|
|
|
p->playing = false;
|
|
|
|
state->playing = p->playing;
|
2024-04-10 05:12:40 +00:00
|
|
|
ao_wakeup(ao);
|
2022-01-11 16:23:52 +00:00
|
|
|
} else {
|
|
|
|
state->playing = p->playing;
|
|
|
|
}
|
2020-11-25 03:10:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const struct ao_driver audio_out_sndio = {
|
|
|
|
.name = "sndio",
|
|
|
|
.description = "sndio audio output",
|
|
|
|
.init = init,
|
|
|
|
.uninit = uninit,
|
|
|
|
.control = control,
|
|
|
|
.reset = reset,
|
|
|
|
.start = start,
|
|
|
|
.write = audio_write,
|
|
|
|
.get_state = get_state,
|
|
|
|
.priv_size = sizeof(struct priv),
|
|
|
|
};
|