summaryrefslogtreecommitdiffstats
path: root/audio
diff options
context:
space:
mode:
authorRudolf Polzer <divverent@xonotic.org>2012-12-28 08:07:14 +0100
committerRudolf Polzer <divverent@xonotic.org>2012-12-28 08:40:28 +0100
commitc3cc38e4c40d23178330e035c8befc0443a2868b (patch)
treebbcb141fb3c1e044b392297c310850aff6e63fd4 /audio
parente830b004580a547c1d27f106ca493c70712ec1a6 (diff)
downloadmpv-c3cc38e4c40d23178330e035c8befc0443a2868b.tar.bz2
mpv-c3cc38e4c40d23178330e035c8befc0443a2868b.tar.xz
vo/ao: SDL 1.2+ audio driver, SDL 2.0+ accelerated video driver
This mainly serves as a fallback for platforms where nothing better is available; also as a debugging help. Both the audio and video driver are not first class - the audio driver lacks delay detection, and the video driver only supports a single YUV color space. Configure options: --disable-sdl2 to disable SDL 2.0+ detection, --disable-sdl to disable SDL 1.2+ detection. Both options need to be specified to turn off SDL support entirely.
Diffstat (limited to 'audio')
-rw-r--r--audio/out/ao.c4
-rw-r--r--audio/out/ao_sdl.c379
2 files changed, 383 insertions, 0 deletions
diff --git a/audio/out/ao.c b/audio/out/ao.c
index 915af93793..85e9548454 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -45,6 +45,7 @@ extern const struct ao_driver audio_out_pcm;
extern const struct ao_driver audio_out_pss;
extern const struct ao_driver audio_out_lavc;
extern const struct ao_driver audio_out_portaudio;
+extern const struct ao_driver audio_out_sdl;
static const struct ao_driver * const audio_out_drivers[] = {
// native:
@@ -73,6 +74,9 @@ static const struct ao_driver * const audio_out_drivers[] = {
#ifdef CONFIG_OPENAL
&audio_out_openal,
#endif
+#ifdef CONFIG_SDL
+ &audio_out_sdl,
+#endif
&audio_out_null,
// should not be auto-selected:
&audio_out_pcm,
diff --git a/audio/out/ao_sdl.c b/audio/out/ao_sdl.c
new file mode 100644
index 0000000000..26d8717602
--- /dev/null
+++ b/audio/out/ao_sdl.c
@@ -0,0 +1,379 @@
+/*
+ * SDL2 audio output
+ * Copyright (C) 2012 Rudolf Polzer <divVerent@xonotic.org>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "audio/format.h"
+#include "talloc.h"
+#include "ao.h"
+#include "core/mp_msg.h"
+#include "core/subopt-helper.h"
+#include "osdep/timer.h"
+
+#include <libavutil/fifo.h>
+#include <SDL.h>
+
+// hack because SDL can't be asked about the current delay
+#define ESTIMATE_DELAY
+
+struct priv
+{
+ AVFifoBuffer *buffer;
+ SDL_mutex *buffer_mutex;
+ SDL_cond *underrun_cond;
+ bool unpause;
+ bool paused;
+#ifdef ESTIMATE_DELAY
+ unsigned int callback_time0;
+ unsigned int callback_time1;
+#endif
+};
+
+static void audio_callback(void *userdata, Uint8 *stream, int len)
+{
+ struct ao *ao = userdata;
+ struct priv *priv = ao->priv;
+
+ SDL_LockMutex(priv->buffer_mutex);
+
+#ifdef ESTIMATE_DELAY
+ priv->callback_time1 = priv->callback_time0;
+ priv->callback_time0 = GetTimer();
+#endif
+
+ while (len > 0 && !priv->paused) {
+ int got = av_fifo_size(priv->buffer);
+ if (got > len)
+ got = len;
+ if (got > 0) {
+ av_fifo_generic_read(priv->buffer, stream, got, NULL);
+ len -= got;
+ stream += got;
+ }
+ if (len > 0)
+ SDL_CondWait(priv->underrun_cond, priv->buffer_mutex);
+ }
+
+ SDL_UnlockMutex(priv->buffer_mutex);
+}
+
+static void uninit(struct ao *ao, bool cut_audio)
+{
+ struct priv *priv = ao->priv;
+ if (!priv)
+ return;
+
+ // abort the callback
+ priv->paused = 1;
+
+ if (SDL_WasInit(SDL_INIT_AUDIO)) {
+ if (priv->buffer_mutex)
+ SDL_LockMutex(priv->buffer_mutex);
+ if (priv->underrun_cond)
+ SDL_CondSignal(priv->underrun_cond);
+ if (priv->buffer_mutex)
+ SDL_UnlockMutex(priv->buffer_mutex);
+
+ // make sure the callback exits
+ SDL_LockAudio();
+
+ // close audio device
+ SDL_QuitSubSystem(SDL_INIT_AUDIO);
+ }
+
+ // get rid of the mutex
+ if (priv->underrun_cond)
+ SDL_DestroyCond(priv->underrun_cond);
+ if (priv->buffer_mutex)
+ SDL_DestroyMutex(priv->buffer_mutex);
+ if (priv->buffer)
+ av_fifo_free(priv->buffer);
+
+ talloc_free(ao->priv);
+ ao->priv = NULL;
+}
+
+static unsigned int ceil_power_of_two(unsigned int x)
+{
+ int y = 1;
+ while (y < x)
+ y *= 2;
+ return y;
+}
+
+static void print_help(void) {
+ mp_msg(MSGT_AO, MSGL_FATAL,
+ "\n-ao sdl commandline help:\n"
+ "Example: mpv -ao sdl:buflen=len\n"
+ "\nOptions:\n"
+ " buflen=len\n"
+ " Length of audio buffer in seconds\n"
+ " bufcnt=cnt\n"
+ " Count of extra audio buffers\n"
+ );
+}
+
+static int init(struct ao *ao, char *params)
+{
+ if (SDL_WasInit(SDL_INIT_AUDIO)) {
+ mp_msg(MSGT_AO, MSGL_ERR, "[sdl] already initialized\n");
+ return -1;
+ }
+
+ float buflen = 0; // use SDL default
+ float bufcnt = 2;
+ const opt_t subopts[] = {
+ {"buflen", OPT_ARG_FLOAT, &buflen, NULL},
+ {"bufcnt", OPT_ARG_FLOAT, &bufcnt, NULL},
+ {NULL}
+ };
+ if (subopt_parse(params, subopts) != 0) {
+ print_help();
+ return -1;
+ }
+
+ struct priv *priv = talloc_zero(ao, struct priv);
+ ao->priv = priv;
+
+ if (SDL_InitSubSystem(SDL_INIT_AUDIO)) {
+ if (!ao->probing)
+ mp_msg(MSGT_AO, MSGL_ERR, "[sdl] SDL_Init failed\n");
+ uninit(ao, true);
+ return -1;
+ }
+
+ SDL_AudioSpec desired, obtained;
+
+ int bytes = 0;
+ switch (ao->format) {
+ case AF_FORMAT_U8: desired.format = AUDIO_U8; bytes = 1; break;
+ case AF_FORMAT_S8: desired.format = AUDIO_S8; bytes = 1; break;
+ case AF_FORMAT_U16_LE: desired.format = AUDIO_U16LSB; bytes = 2; break;
+ case AF_FORMAT_U16_BE: desired.format = AUDIO_U16MSB; bytes = 2; break;
+ default:
+ case AF_FORMAT_S16_LE: desired.format = AUDIO_S16LSB; bytes = 2; break;
+ case AF_FORMAT_S16_BE: desired.format = AUDIO_S16MSB; bytes = 2; break;
+#ifdef AUDIO_S32LSB
+ case AF_FORMAT_S32_LE: desired.format = AUDIO_S32LSB; bytes = 4; break;
+#endif
+#ifdef AUDIO_S32MSB
+ case AF_FORMAT_S32_BE: desired.format = AUDIO_S32MSB; bytes = 4; break;
+#endif
+#ifdef AUDIO_F32LSB
+ case AF_FORMAT_FLOAT_LE: desired.format = AUDIO_F32LSB; bytes = 4; break;
+#endif
+#ifdef AUDIO_F32MSB
+ case AF_FORMAT_FLOAT_BE: desired.format = AUDIO_F32MSB; bytes = 4; break;
+#endif
+ }
+ desired.freq = ao->samplerate;
+ desired.channels = ao->channels;
+ desired.samples = FFMIN(32768, ceil_power_of_two(ao->samplerate * buflen));
+ desired.callback = audio_callback;
+ desired.userdata = ao;
+
+ mp_msg(MSGT_AO, MSGL_V, "[sdl] requested format: %d Hz, %d channels, %x, "
+ "buffer size: %d samples\n",
+ (int) desired.freq, (int) desired.channels,
+ (int) desired.format, (int) desired.samples);
+
+ obtained = desired;
+ if (SDL_OpenAudio(&desired, &obtained)) {
+ if (!ao->probing)
+ mp_msg(MSGT_AO, MSGL_ERR, "[sdl] could not open audio: %s\n",
+ SDL_GetError());
+ uninit(ao, true);
+ return -1;
+ }
+
+ mp_msg(MSGT_AO, MSGL_V, "[sdl] obtained format: %d Hz, %d channels, %x, "
+ "buffer size: %d samples\n",
+ (int) obtained.freq, (int) obtained.channels,
+ (int) obtained.format, (int) obtained.samples);
+
+ switch (obtained.format) {
+ case AUDIO_U8: ao->format = AF_FORMAT_U8; bytes = 1; break;
+ case AUDIO_S8: ao->format = AF_FORMAT_S8; bytes = 1; break;
+ case AUDIO_S16LSB: ao->format = AF_FORMAT_S16_LE; bytes = 2; break;
+ case AUDIO_S16MSB: ao->format = AF_FORMAT_S16_BE; bytes = 2; break;
+ case AUDIO_U16LSB: ao->format = AF_FORMAT_U16_LE; bytes = 2; break;
+ case AUDIO_U16MSB: ao->format = AF_FORMAT_U16_BE; bytes = 2; break;
+#ifdef AUDIO_S32LSB
+ case AUDIO_S32LSB: ao->format = AF_FORMAT_S32_LE; bytes = 4; break;
+#endif
+#ifdef AUDIO_S32MSB
+ case AUDIO_S32MSB: ao->format = AF_FORMAT_S32_BE; bytes = 4; break;
+#endif
+#ifdef AUDIO_F32LSB
+ case AUDIO_F32LSB: ao->format = AF_FORMAT_FLOAT_LE; bytes = 4; break;
+#endif
+#ifdef AUDIO_F32MSB
+ case AUDIO_F32MSB: ao->format = AF_FORMAT_FLOAT_BE; bytes = 4; break;
+#endif
+ default:
+ if (!ao->probing)
+ mp_msg(MSGT_AO, MSGL_ERR,
+ "[sdl] could not find matching format\n");
+ uninit(ao, true);
+ return -1;
+ }
+
+ ao->samplerate = obtained.freq;
+ ao->channels = obtained.channels;
+ ao->bps = ao->channels * ao->samplerate * bytes;
+ ao->buffersize = obtained.size * bufcnt;
+ ao->outburst = obtained.size;
+ priv->buffer = av_fifo_alloc(ao->buffersize);
+ priv->buffer_mutex = SDL_CreateMutex();
+ if (!priv->buffer_mutex) {
+ mp_msg(MSGT_AO, MSGL_ERR, "[sdl] SDL_CreateMutex failed\n");
+ uninit(ao, true);
+ return -1;
+ }
+ priv->underrun_cond = SDL_CreateCond();
+ if (!priv->underrun_cond) {
+ mp_msg(MSGT_AO, MSGL_ERR, "[sdl] SDL_CreateCond failed\n");
+ uninit(ao, true);
+ return -1;
+ }
+
+ priv->unpause = 1;
+ priv->paused = 1;
+ priv->callback_time0 = priv->callback_time1 = GetTimer();
+
+ return 1;
+}
+
+static void reset(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ SDL_LockMutex(priv->buffer_mutex);
+ av_fifo_reset(priv->buffer);
+ SDL_UnlockMutex(priv->buffer_mutex);
+}
+
+static int get_space(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ SDL_LockMutex(priv->buffer_mutex);
+ int space = av_fifo_space(priv->buffer);
+ SDL_UnlockMutex(priv->buffer_mutex);
+ return space;
+}
+
+static void pause(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ SDL_PauseAudio(SDL_TRUE);
+ priv->unpause = 0;
+ priv->paused = 1;
+ SDL_CondSignal(priv->underrun_cond);
+}
+
+static void do_resume(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ priv->paused = 0;
+ SDL_PauseAudio(SDL_FALSE);
+}
+
+static void resume(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ SDL_LockMutex(priv->buffer_mutex);
+ int free = av_fifo_space(priv->buffer);
+ SDL_UnlockMutex(priv->buffer_mutex);
+ if (free)
+ priv->unpause = 1;
+ else
+ do_resume(ao);
+}
+
+static int play(struct ao *ao, void *data, int len, int flags)
+{
+ struct priv *priv = ao->priv;
+ SDL_LockMutex(priv->buffer_mutex);
+ int free = av_fifo_space(priv->buffer);
+ if (len > free) len = free;
+ av_fifo_generic_write(priv->buffer, data, len, NULL);
+ SDL_CondSignal(priv->underrun_cond);
+ SDL_UnlockMutex(priv->buffer_mutex);
+ if (priv->unpause) {
+ priv->unpause = 0;
+ do_resume(ao);
+ }
+ return len;
+}
+
+static float get_delay(struct ao *ao)
+{
+ struct priv *priv = ao->priv;
+ SDL_LockMutex(priv->buffer_mutex);
+ int sz = av_fifo_size(priv->buffer);
+#ifdef ESTIMATE_DELAY
+ unsigned int callback_time0 = priv->callback_time0;
+ unsigned int callback_time1 = priv->callback_time1;
+#endif
+ SDL_UnlockMutex(priv->buffer_mutex);
+
+ // delay component: our FIFO's length
+ float delay = sz / (float) ao->bps;
+
+#ifdef ESTIMATE_DELAY
+ // delay component: outstanding audio living in SDL
+
+ unsigned int current_time = GetTimer();
+
+ // interval between callbacks
+ unsigned int callback_interval = callback_time0 - callback_time1;
+ unsigned int elapsed_interval = current_time - callback_time0;
+ if (elapsed_interval > callback_interval)
+ elapsed_interval = callback_interval;
+
+ // delay subcomponent: remaining audio from the currently played buffer
+ unsigned int buffer_interval = callback_interval - elapsed_interval;
+
+ // delay subcomponent: remaining audio from the next played buffer, as
+ // provided by the callback
+ buffer_interval += callback_interval;
+
+ delay += buffer_interval / 1000000.0;
+#endif
+
+ return delay;
+}
+
+const struct ao_driver audio_out_sdl = {
+ .is_new = true,
+ .info = &(const struct ao_info) {
+ "SDL Audio",
+ "sdl",
+ "Rudolf Polzer <divVerent@xonotic.org>",
+ ""
+ },
+ .init = init,
+ .uninit = uninit,
+ .get_space = get_space,
+ .play = play,
+ .get_delay = get_delay,
+ .pause = pause,
+ .resume = resume,
+ .reset = reset,
+};