summaryrefslogtreecommitdiffstats
path: root/audio/out/ao_sdl.c
diff options
context:
space:
mode:
Diffstat (limited to 'audio/out/ao_sdl.c')
-rw-r--r--audio/out/ao_sdl.c216
1 files changed, 216 insertions, 0 deletions
diff --git a/audio/out/ao_sdl.c b/audio/out/ao_sdl.c
new file mode 100644
index 0000000..5a6a58b
--- /dev/null
+++ b/audio/out/ao_sdl.c
@@ -0,0 +1,216 @@
+/*
+ * audio output driver for SDL 1.2+
+ * Copyright (C) 2012 Rudolf Polzer <divVerent@xonotic.org>
+ *
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "audio/format.h"
+#include "mpv_talloc.h"
+#include "ao.h"
+#include "internal.h"
+#include "common/common.h"
+#include "common/msg.h"
+#include "options/m_option.h"
+#include "osdep/timer.h"
+
+#include <SDL.h>
+
+struct priv
+{
+ bool paused;
+
+ float buflen;
+};
+
+static const int fmtmap[][2] = {
+ {AF_FORMAT_U8, AUDIO_U8},
+ {AF_FORMAT_S16, AUDIO_S16SYS},
+#ifdef AUDIO_S32SYS
+ {AF_FORMAT_S32, AUDIO_S32SYS},
+#endif
+#ifdef AUDIO_F32SYS
+ {AF_FORMAT_FLOAT, AUDIO_F32SYS},
+#endif
+ {0}
+};
+
+static void audio_callback(void *userdata, Uint8 *stream, int len)
+{
+ struct ao *ao = userdata;
+
+ void *data[1] = {stream};
+
+ if (len % ao->sstride)
+ MP_ERR(ao, "SDL audio callback not sample aligned");
+
+ // Time this buffer will take, plus assume 1 period (1 callback invocation)
+ // fixed latency.
+ double delay = 2 * len / (double)ao->bps;
+
+ ao_read_data(ao, data, len / ao->sstride, mp_time_ns() + MP_TIME_S_TO_NS(delay));
+}
+
+static void uninit(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ if (!priv)
+ return;
+
+ if (SDL_WasInit(SDL_INIT_AUDIO)) {
+ // make sure the callback exits
+ SDL_LockAudio();
+
+ // close audio device
+ SDL_QuitSubSystem(SDL_INIT_AUDIO);
+ }
+}
+
+static unsigned int ceil_power_of_two(unsigned int x)
+{
+ int y = 1;
+ while (y < x)
+ y *= 2;
+ return y;
+}
+
+static int init(struct ao *ao)
+{
+ if (SDL_WasInit(SDL_INIT_AUDIO)) {
+ MP_ERR(ao, "already initialized\n");
+ return -1;
+ }
+
+ struct priv *priv = ao->priv;
+
+ if (SDL_InitSubSystem(SDL_INIT_AUDIO)) {
+ if (!ao->probing)
+ MP_ERR(ao, "SDL_Init failed\n");
+ uninit(ao);
+ return -1;
+ }
+
+ struct mp_chmap_sel sel = {0};
+ mp_chmap_sel_add_waveext_def(&sel);
+ if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels)) {
+ uninit(ao);
+ return -1;
+ }
+
+ ao->format = af_fmt_from_planar(ao->format);
+
+ SDL_AudioSpec desired = {0};
+ desired.format = AUDIO_S16SYS;
+ for (int n = 0; fmtmap[n][0]; n++) {
+ if (ao->format == fmtmap[n][0]) {
+ desired.format = fmtmap[n][1];
+ break;
+ }
+ }
+ desired.freq = ao->samplerate;
+ desired.channels = ao->channels.num;
+ if (priv->buflen) {
+ desired.samples = MPMIN(32768, ceil_power_of_two(ao->samplerate *
+ priv->buflen));
+ }
+ desired.callback = audio_callback;
+ desired.userdata = ao;
+
+ MP_VERBOSE(ao, "requested format: %d Hz, %d channels, %x, "
+ "buffer size: %d samples\n",
+ (int) desired.freq, (int) desired.channels,
+ (int) desired.format, (int) desired.samples);
+
+ SDL_AudioSpec obtained = desired;
+ if (SDL_OpenAudio(&desired, &obtained)) {
+ if (!ao->probing)
+ MP_ERR(ao, "could not open audio: %s\n", SDL_GetError());
+ uninit(ao);
+ return -1;
+ }
+
+ MP_VERBOSE(ao, "obtained format: %d Hz, %d channels, %x, "
+ "buffer size: %d samples\n",
+ (int) obtained.freq, (int) obtained.channels,
+ (int) obtained.format, (int) obtained.samples);
+
+ // The sample count is usually the number of samples the callback requests,
+ // which we assume is the period size. Normally, ao.c will allocate a large
+ // enough buffer. But in case the period size should be pathologically
+ // large, this will help.
+ ao->device_buffer = 3 * obtained.samples;
+
+ ao->format = 0;
+ for (int n = 0; fmtmap[n][0]; n++) {
+ if (obtained.format == fmtmap[n][1]) {
+ ao->format = fmtmap[n][0];
+ break;
+ }
+ }
+ if (!ao->format) {
+ if (!ao->probing)
+ MP_ERR(ao, "could not find matching format\n");
+ uninit(ao);
+ return -1;
+ }
+
+ if (!ao_chmap_sel_get_def(ao, &sel, &ao->channels, obtained.channels)) {
+ uninit(ao);
+ return -1;
+ }
+
+ ao->samplerate = obtained.freq;
+
+ priv->paused = 1;
+
+ return 1;
+}
+
+static void reset(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ if (!priv->paused)
+ SDL_PauseAudio(SDL_TRUE);
+ priv->paused = 1;
+}
+
+static void start(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ if (priv->paused)
+ SDL_PauseAudio(SDL_FALSE);
+ priv->paused = 0;
+}
+
+#define OPT_BASE_STRUCT struct priv
+
+const struct ao_driver audio_out_sdl = {
+ .description = "SDL Audio",
+ .name = "sdl",
+ .init = init,
+ .uninit = uninit,
+ .reset = reset,
+ .start = start,
+ .priv_size = sizeof(struct priv),
+ .priv_defaults = &(const struct priv) {
+ .buflen = 0, // use SDL default
+ },
+ .options = (const struct m_option[]) {
+ {"buflen", OPT_FLOAT(buflen)},
+ {0}
+ },
+ .options_prefix = "sdl",
+};