summaryrefslogtreecommitdiffstats
path: root/audio/out/ao_sndio.c
diff options
context:
space:
mode:
Diffstat (limited to 'audio/out/ao_sndio.c')
-rw-r--r--audio/out/ao_sndio.c330
1 files changed, 167 insertions, 163 deletions
diff --git a/audio/out/ao_sndio.c b/audio/out/ao_sndio.c
index e5938b7d26..309ea4ab74 100644
--- a/audio/out/ao_sndio.c
+++ b/audio/out/ao_sndio.c
@@ -1,6 +1,8 @@
/*
* 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>
+ * Copyright (c) 2021 Andrew Krasavin <noiseless-ak@yandex.ru>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -15,13 +17,13 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include "config.h"
-
#include <sys/types.h>
#include <poll.h>
#include <errno.h>
#include <sndio.h>
+#include "config.h"
+
#include "options/m_option.h"
#include "common/msg.h"
@@ -36,78 +38,63 @@ struct priv {
bool playing;
int vol;
int havevol;
-#define SILENCE_NMAX 0x1000
- char silence[SILENCE_NMAX];
struct pollfd *pfd;
- char *dev;
};
-/*
- * misc parameters (volume, etc...)
- */
-static int control(struct ao *ao, enum aocontrol cmd, void *arg)
+
+static const struct mp_chmap sndio_layouts[] = {
+ {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;
- ao_control_vol_t *vol = arg;
- switch (cmd) {
- case AOCONTROL_GET_VOLUME:
- if (!p->havevol)
- return CONTROL_FALSE;
- vol->left = vol->right = p->vol * 100 / SIO_MAXVOL;
- break;
- case AOCONTROL_SET_VOLUME:
- if (!p->havevol)
- return CONTROL_FALSE;
- sio_setvol(p->hdl, vol->left * SIO_MAXVOL / 100);
- break;
- default:
- return CONTROL_UNKNOWN;
- }
- return CONTROL_OK;
+ int n = sio_pollfd(p->hdl, p->pfd, POLLOUT);
+ while (poll(p->pfd, n, 0) < 0 && errno == EINTR);
+
+ sio_revents(p->hdl, p->pfd);
}
-/*
- * call-back invoked to notify of the hardware position
- */
+/* Call-back invoked to notify of the hardware position. */
static void movecb(void *addr, int delta)
{
- struct priv *p = addr;
+ struct ao *ao = addr;
+ struct priv *p = ao->priv;
+
p->delay -= delta;
}
-/*
- * call-back invoked to notify about volume changes
- */
+/* Call-back invoked to notify about volume changes. */
static void volcb(void *addr, unsigned newvol)
{
- struct priv *p = addr;
+ struct ao *ao = addr;
+ struct priv *p = ao->priv;
+
p->vol = newvol;
}
-static const struct mp_chmap sndio_layouts[MP_NUM_CHANNELS + 1] = {
- {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. */
-};
-
-/*
- * open device and setup parameters
- * return: 0=success -1=fail
- */
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;
};
@@ -117,59 +104,62 @@ static int init(struct ao *ao)
{AF_FORMAT_S32, 32, 1},
};
const struct af_to_par *ap;
- int i;
+ const char *device = ((ao->device) ? ao->device : SIO_DEVANY);
- p->hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0);
+ /* 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 sndio %s\n", SIO_DEVANY);
- goto error;
+ 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);
- sio_initpar(&p->par);
- for (i = 0, ap = af_to_par;; i++, ap++) {
- if (i == sizeof(af_to_par) / sizeof(struct af_to_par)) {
- MP_VERBOSE(ao, "unsupported format\n");
- p->par.bits = 16;
- p->par.sig = 1;
- p->par.le = SIO_LE_NATIVE;
- break;
- }
+ 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];
if (ap->format == ao->format) {
p->par.bits = ap->bits;
p->par.sig = ap->sig;
- if (ap->bits > 8)
- p->par.le = SIO_LE_NATIVE;
- if (ap->bits != SIO_BPS(ap->bits))
- p->par.bps = ap->bits / 8;
break;
}
}
- p->par.rate = ao->samplerate;
- struct mp_chmap_sel sel = {0};
- for (int n = 0; n < MP_NUM_CHANNELS+1; n++)
- mp_chmap_sel_add_map(&sel, &sndio_layouts[n]);
+ 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 error;
+ goto err_out;
p->par.pchan = ao->channels.num;
p->par.appbufsz = p->par.rate * 250 / 1000; /* 250ms buffer */
p->par.round = p->par.rate * 10 / 1000; /* 10ms block size */
+
if (!sio_setpar(p->hdl, &p->par)) {
MP_ERR(ao, "couldn't set params\n");
- goto error;
+ goto err_out;
}
+
+ /* Get current sound params. */
if (!sio_getpar(p->hdl, &p->par)) {
MP_ERR(ao, "couldn't get params\n");
- goto error;
+ goto err_out;
}
if (p->par.bps > 1 && p->par.le != SIO_LE_NATIVE) {
MP_ERR(ao, "swapped endian output not supported\n");
- goto error;
+ 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) {
@@ -178,142 +168,156 @@ static int init(struct ao *ao)
ao->format = AF_FORMAT_S32;
} else {
MP_ERR(ao, "couldn't set format\n");
- goto error;
+ goto err_out;
}
- p->havevol = sio_onvol(p->hdl, volcb, p);
- sio_onmove(p->hdl, movecb, p);
- if (!sio_start(p->hdl))
- MP_ERR(ao, "init: couldn't start\n");
+ p->havevol = sio_onvol(p->hdl, volcb, ao);
+ sio_onmove(p->hdl, movecb, ao);
- p->pfd = calloc (sio_nfds(p->hdl), sizeof (struct pollfd));
+ p->pfd = talloc_array_ptrtype(p, p->pfd, sio_nfds(p->hdl));
if (!p->pfd)
- goto error;
+ goto err_out;
- ao->period_size = p->par.round;
+ 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);
- return 0;
+ p->delay = 0;
+ p->playing = false;
+ if (!sio_start(p->hdl)) {
+ MP_ERR(ao, "start: sio_start() fail.\n");
+ goto err_out;
+ }
-error:
- if (p->hdl)
- sio_close(p->hdl);
+ return 0;
+err_out:
+ uninit(ao);
return -1;
}
-/*
- * close device
- */
static void uninit(struct ao *ao)
{
struct priv *p = ao->priv;
- if (p->hdl)
+ 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;
+ float *vol = arg;
- free(p->pfd);
+ switch (cmd) {
+ case AOCONTROL_GET_VOLUME:
+ if (!p->havevol)
+ return CONTROL_FALSE;
+ *vol = p->vol * 100 / SIO_MAXVOL;
+ break;
+ case AOCONTROL_SET_VOLUME:
+ if (!p->havevol)
+ return CONTROL_FALSE;
+ sio_setvol(p->hdl, *vol * SIO_MAXVOL / 100);
+ break;
+ default:
+ return CONTROL_UNKNOWN;
+ }
+ return CONTROL_OK;
}
-/*
- * stop playing and empty buffers (for seeking/pause)
- */
static void reset(struct ao *ao)
{
struct priv *p = ao->priv;
if (p->playing) {
- MP_WARN(ao, "Blocking until remaining audio is played... (sndio design bug).\n");
-
p->playing = false;
- if (!sio_stop(p->hdl))
- MP_ERR(ao, "reset: couldn't stop\n");
+#if HAVE_SNDIO_1_9
+ if (!sio_flush(p->hdl)) {
+ MP_ERR(ao, "reset: couldn't sio_flush()\n");
+#else
+ if (!sio_stop(p->hdl)) {
+ MP_ERR(ao, "reset: couldn't sio_stop()\n");
+#endif
+ }
p->delay = 0;
- if (!sio_start(p->hdl))
- MP_ERR(ao, "reset: couldn't start\n");
+ if (!sio_start(p->hdl)) {
+ MP_ERR(ao, "reset: sio_start() fail.\n");
+ }
}
}
-/*
- * play given number of samples until sio_write() blocks
- */
-static int play(struct ao *ao, void **data, int samples, int flags)
+static void start(struct ao *ao)
{
struct priv *p = ao->priv;
- int n;
- n = sio_write(p->hdl, data[0], samples * ao->sstride) / ao->sstride;
- p->delay += n;
p->playing = true;
- /* on AOPLAY_FINAL_CHUNK, just let it underrun */
- return n;
-}
-
-/*
- * make libsndio call movecb()
- */
-static void update(struct ao *ao)
-{
- struct priv *p = ao->priv;
- int n = sio_pollfd(p->hdl, p->pfd, POLLOUT);
- while (poll(p->pfd, n, 0) < 0 && errno == EINTR) {}
- sio_revents(p->hdl, p->pfd);
+ process_events(ao);
}
-/*
- * how many samples can be played without blocking
- */
-static int get_space(struct ao *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;
- update(ao);
-
- int samples = p->par.bufsz - p->delay;
- return samples / p->par.round * p->par.round;
+ return true;
}
-/*
- * return: delay in seconds between first and last sample in buffer
- */
-static double get_delay(struct ao *ao)
+static void get_state(struct ao *ao, struct mp_pcm_state *state)
{
struct priv *p = ao->priv;
- update(ao);
-
- return p->delay / (double)p->par.rate;
-}
-
-/*
- * stop playing, keep buffers (for pause)
- */
-static void audio_pause(struct ao *ao)
-{
- reset(ao);
-}
-
-/*
- * resume playing, after audio_pause()
- */
-static void audio_resume(struct ao *ao)
-{
- return;
+ process_events(ao);
+
+ /* 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 */
+ state->queued_samples = p->delay;
+ /* delay in seconds between first and last sample in buffer */
+ state->delay = p->delay / (double)p->par.rate;
+
+ /* report unexpected EOF / underrun */
+ if ((state->queued_samples &&
+ (state->queued_samples < state->free_samples) &&
+ p->playing) || sio_eof(p->hdl))
+ {
+ 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;
+ ao_wakeup(ao);
+ } else {
+ state->playing = p->playing;
+ }
}
-#define OPT_BASE_STRUCT struct priv
-
const struct ao_driver audio_out_sndio = {
- .description = "sndio audio output",
.name = "sndio",
+ .description = "sndio 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,
+ .start = start,
+ .write = audio_write,
+ .get_state = get_state,
.priv_size = sizeof(struct priv),
};