summaryrefslogtreecommitdiffstats
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
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.
-rw-r--r--DOCS/man/en/ao.rst15
-rw-r--r--DOCS/man/en/vo.rst5
-rw-r--r--Makefile2
-rw-r--r--audio/out/ao.c4
-rw-r--r--audio/out/ao_sdl.c379
-rwxr-xr-xconfigure45
-rw-r--r--video/out/vo.c4
-rw-r--r--video/out/vo_sdl.c1004
8 files changed, 1458 insertions, 0 deletions
diff --git a/DOCS/man/en/ao.rst b/DOCS/man/en/ao.rst
index ae0432784f..dc7b7fccb1 100644
--- a/DOCS/man/en/ao.rst
+++ b/DOCS/man/en/ao.rst
@@ -92,6 +92,21 @@ dsound (Windows only)
Sets the device number to use. Playing a file with ``-v`` will show a
list of available devices.
+sdl
+ SDL 1.2+ audio output driver. Should work everywhere where SDL 1.2 builds,
+ but may require the SDL_AUDIODRIVER environment variable to be set
+ appropriately for your system.
+
+ buflen=<length>
+ Sets the audio buffer length in seconds. Is used only approximately,
+ or even disaregarded entirely by the sound system. Playing a file with
+ ``-v`` will show the requested and obtained exact buffer size. A value
+ of 0 selects the sound system default.
+
+ bufcnt=<count>
+ Sets the number of extra audio buffers in mpv. Usually needs not be
+ changed.
+
null
Produces no audio output but maintains video playback speed. Use
``--no-audio`` for benchmarking.
diff --git a/DOCS/man/en/vo.rst b/DOCS/man/en/vo.rst
index 1bfe5fbf1f..cf92d97efa 100644
--- a/DOCS/man/en/vo.rst
+++ b/DOCS/man/en/vo.rst
@@ -660,6 +660,11 @@ opengl-old
x11
X11/GLX
+sdl
+ SDL 2.0+ Render video output driver, depending on system with or without
+ hardware acceleration. Should work everywhere where SDL 2.0 builds. For
+ tuning, refer to your copy of the file SDL_hints.h.
+
null
Produces no video output. Useful for benchmarking.
diff --git a/Makefile b/Makefile
index 9999145c83..738d6bd20e 100644
--- a/Makefile
+++ b/Makefile
@@ -89,6 +89,8 @@ SOURCES-$(ALSA) += audio/out/ao_alsa.c
SOURCES-$(APPLE_IR) += core/input/appleir.c
SOURCES-$(APPLE_REMOTE) += core/input/ar.c
SOURCES-$(CACA) += video/out/vo_caca.c
+SOURCES-$(SDL) += audio/out/ao_sdl.c
+SOURCES-$(SDL2) += video/out/vo_sdl.c
SOURCES-$(COREAUDIO) += audio/out/ao_coreaudio.c
SOURCES-$(COREVIDEO) += video/out/vo_corevideo.m
SOURCES-$(DIRECT3D) += video/out/vo_direct3d.c \
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,
+};
diff --git a/configure b/configure
index 02d8f070be..f106876cd4 100755
--- a/configure
+++ b/configure
@@ -348,6 +348,8 @@ Video output:
--enable-gl enable OpenGL video output [autodetect]
--enable-caca enable CACA video output [autodetect]
--enable-direct3d enable Direct3D video output [autodetect]
+ --enable-sdl enable SDL audio output [autodetect]
+ --enable-sdl2 enable SDL 2.0+ audio and video output [autodetect]
--enable-xv enable Xv video output [autodetect]
--enable-vdpau enable VDPAU acceleration [autodetect]
--enable-vm enable XF86VidMode support [autodetect]
@@ -422,6 +424,8 @@ _xss=auto
_xv=auto
_vdpau=auto
_direct3d=auto
+_sdl=auto
+_sdl2=auto
_dsound=auto
_nas=auto
_mng=auto
@@ -595,6 +599,10 @@ for ac_option do
--disable-vdpau) _vdpau=no ;;
--enable-direct3d) _direct3d=yes ;;
--disable-direct3d) _direct3d=no ;;
+ --enable-sdl) _sdl=yes ;;
+ --disable-sdl) _sdl=no ;;
+ --enable-sdl2) _sdl2=yes ;;
+ --disable-sdl2) _sdl2=no ;;
--enable-dsound) _dsound=yes ;;
--disable-dsound) _dsound=no ;;
--enable-mng) _mng=yes ;;
@@ -2319,6 +2327,39 @@ echores "$_dsound"
fi #if win32; then
+echocheck "SDL 2.0"
+if test "$_sdl2" = auto ; then
+ pkg_config_add 'sdl2' && _sdl2=yes
+fi
+if test "$_sdl2" = yes ; then
+ _sdl=yes # sdl2 implies sdl
+ def_sdl='#define CONFIG_SDL 1'
+ def_sdl2='#define CONFIG_SDL2 1'
+ vomodules="sdl $vomodules"
+ aomodules="sdl $aomodules"
+ echores "$_sdl2"
+else
+ def_sdl2='#undef CONFIG_SDL2'
+ echores "$_sdl2"
+ echocheck "SDL"
+ if test "$_sdl" = auto ; then
+ pkg_config_add 'sdl' && _sdl=yes
+ fi
+ if test "$_sdl" = yes ; then
+ def_sdl='#define CONFIG_SDL 1'
+ novomodules="sdl $novomodules"
+ aomodules="sdl $aomodules"
+ else
+ def_sdl='#undef CONFIG_SDL'
+ novomodules="sdl $novomodules"
+ noaomodules="sdl $noaomodules"
+ fi
+ echores "$_sdl"
+fi
+
+
+
+
#########
# AUDIO #
#########
@@ -3159,6 +3200,8 @@ COCOA = $_cocoa
COREAUDIO = $_coreaudio
COREVIDEO = $_corevideo
DIRECT3D = $_direct3d
+SDL = $_sdl
+SDL2 = $_sdl2
DSOUND = $_dsound
DVBIN = $_dvbin
DVDREAD = $_dvdread
@@ -3405,6 +3448,8 @@ $def_caca
$def_corevideo
$def_cocoa
$def_direct3d
+$def_sdl
+$def_sdl2
$def_dsound
$def_dvb
$def_dvbin
diff --git a/video/out/vo.c b/video/out/vo.c
index 04bd9cbb1b..7ec9ccdaf7 100644
--- a/video/out/vo.c
+++ b/video/out/vo.c
@@ -83,6 +83,7 @@ extern struct vo_driver video_out_lavc;
extern struct vo_driver video_out_caca;
extern struct vo_driver video_out_direct3d;
extern struct vo_driver video_out_direct3d_shaders;
+extern struct vo_driver video_out_sdl;
extern struct vo_driver video_out_corevideo;
const struct vo_driver *video_out_drivers[] =
@@ -110,6 +111,9 @@ const struct vo_driver *video_out_drivers[] =
&video_out_opengl_old,
#endif
#endif
+#ifdef CONFIG_SDL2
+ &video_out_sdl,
+#endif
#ifdef CONFIG_X11
&video_out_x11,
#endif
diff --git a/video/out/vo_sdl.c b/video/out/vo_sdl.c
new file mode 100644
index 0000000000..cef54af4cb
--- /dev/null
+++ b/video/out/vo_sdl.c
@@ -0,0 +1,1004 @@
+/*
+ * video output driver for SDL2
+ *
+ * by divVerent <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 <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <SDL.h>
+
+#include "core/input/input.h"
+#include "core/input/keycodes.h"
+#include "core/mp_fifo.h"
+#include "core/mp_msg.h"
+#include "core/options.h"
+
+#include "osdep/timer.h"
+
+#include "sub/sub.h"
+
+#include "video/mp_image.h"
+#include "video/vfcap.h"
+
+#include "aspect.h"
+#include "config.h"
+#include "vo.h"
+
+struct formatmap_entry {
+ Uint32 sdl;
+ unsigned int mpv;
+ int is_rgba;
+};
+const struct formatmap_entry formats[] = {
+ {SDL_PIXELFORMAT_YV12, IMGFMT_YV12, 0},
+ {SDL_PIXELFORMAT_IYUV, IMGFMT_IYUV, 0},
+ {SDL_PIXELFORMAT_YUY2, IMGFMT_YUY2, 0},
+ {SDL_PIXELFORMAT_UYVY, IMGFMT_UYVY, 0},
+ {SDL_PIXELFORMAT_YVYU, IMGFMT_YVYU, 0},
+#if BYTE_ORDER == BIG_ENDIAN
+ {SDL_PIXELFORMAT_RGBX8888, IMGFMT_RGBA, 0}, // has no alpha -> bad for OSD
+ {SDL_PIXELFORMAT_BGRX8888, IMGFMT_BGRA, 0}, // has no alpha -> bad for OSD
+ {SDL_PIXELFORMAT_ARGB8888, IMGFMT_ARGB, 1},
+ {SDL_PIXELFORMAT_RGBA8888, IMGFMT_RGBA, 1},
+ {SDL_PIXELFORMAT_ABGR8888, IMGFMT_ABGR, 1},
+ {SDL_PIXELFORMAT_BGRA8888, IMGFMT_BGRA, 1},
+ {SDL_PIXELFORMAT_RGB24, IMGFMT_RGB24, 0},
+ {SDL_PIXELFORMAT_BGR24, IMGFMT_BGR24, 0},
+ {SDL_PIXELFORMAT_RGB888, IMGFMT_RGB24, 0},
+ {SDL_PIXELFORMAT_BGR888, IMGFMT_BGR24, 0},
+ {SDL_PIXELFORMAT_RGB565, IMGFMT_RGB16, 0},
+ {SDL_PIXELFORMAT_BGR565, IMGFMT_BGR16, 0},
+ {SDL_PIXELFORMAT_RGB555, IMGFMT_RGB15, 0},
+ {SDL_PIXELFORMAT_BGR555, IMGFMT_BGR15, 0},
+ {SDL_PIXELFORMAT_RGB444, IMGFMT_RGB12, 0}
+#else
+ {SDL_PIXELFORMAT_RGBX8888, IMGFMT_ABGR, 0}, // has no alpha -> bad for OSD
+ {SDL_PIXELFORMAT_BGRX8888, IMGFMT_ARGB, 0}, // has no alpha -> bad for OSD
+ {SDL_PIXELFORMAT_ARGB8888, IMGFMT_BGRA, 1},
+ {SDL_PIXELFORMAT_RGBA8888, IMGFMT_ABGR, 1},
+ {SDL_PIXELFORMAT_ABGR8888, IMGFMT_RGBA, 1},
+ {SDL_PIXELFORMAT_BGRA8888, IMGFMT_ARGB, 1},
+ {SDL_PIXELFORMAT_RGB24, IMGFMT_RGB24, 0},
+ {SDL_PIXELFORMAT_BGR24, IMGFMT_BGR24, 0},
+ {SDL_PIXELFORMAT_RGB888, IMGFMT_BGR24, 0},
+ {SDL_PIXELFORMAT_BGR888, IMGFMT_RGB24, 0},
+ {SDL_PIXELFORMAT_RGB565, IMGFMT_BGR16, 0},
+ {SDL_PIXELFORMAT_BGR565, IMGFMT_RGB16, 0},
+ {SDL_PIXELFORMAT_RGB555, IMGFMT_BGR15, 0},
+ {SDL_PIXELFORMAT_BGR555, IMGFMT_RGB15, 0},
+ {SDL_PIXELFORMAT_RGB444, IMGFMT_BGR12, 0}
+#endif
+};
+
+struct keymap_entry {
+ SDL_Keycode sdl;
+ int mpv;
+};
+const struct keymap_entry keys[] = {
+ {SDLK_RETURN, KEY_ENTER},
+ {SDLK_ESCAPE, KEY_ESC},
+ {SDLK_BACKSPACE, KEY_BACKSPACE},
+ {SDLK_TAB, KEY_TAB},
+ {SDLK_PRINTSCREEN, KEY_PRINT},
+ {SDLK_PAUSE, KEY_PAUSE},
+ {SDLK_INSERT, KEY_INSERT},
+ {SDLK_HOME, KEY_HOME},
+ {SDLK_PAGEUP, KEY_PAGE_UP},
+ {SDLK_DELETE, KEY_DELETE},
+ {SDLK_END, KEY_END},
+ {SDLK_PAGEDOWN, KEY_PAGE_DOWN},
+ {SDLK_RIGHT, KEY_RIGHT},
+ {SDLK_LEFT, KEY_LEFT},
+ {SDLK_DOWN, KEY_DOWN},
+ {SDLK_UP, KEY_UP},
+ {SDLK_KP_ENTER, KEY_KPENTER},
+ {SDLK_KP_1, KEY_KP1},
+ {SDLK_KP_2, KEY_KP2},
+ {SDLK_KP_3, KEY_KP3},
+ {SDLK_KP_4, KEY_KP4},
+ {SDLK_KP_5, KEY_KP5},
+ {SDLK_KP_6, KEY_KP6},
+ {SDLK_KP_7, KEY_KP7},
+ {SDLK_KP_8, KEY_KP8},
+ {SDLK_KP_9, KEY_KP9},
+ {SDLK_KP_0, KEY_KP0},
+ {SDLK_KP_PERIOD, KEY_KPDEC},
+ {SDLK_POWER, KEY_POWER},
+ {SDLK_MENU, KEY_MENU},
+ {SDLK_STOP, KEY_STOP},
+ {SDLK_MUTE, KEY_MUTE},
+ {SDLK_VOLUMEUP, KEY_VOLUME_UP},
+ {SDLK_VOLUMEDOWN, KEY_VOLUME_DOWN},
+ {SDLK_KP_COMMA, KEY_KPDEC},
+ {SDLK_AUDIONEXT, KEY_NEXT},
+ {SDLK_AUDIOPREV, KEY_PREV},
+ {SDLK_AUDIOSTOP, KEY_STOP},
+ {SDLK_AUDIOPLAY, KEY_PLAY},
+ {SDLK_AUDIOMUTE, KEY_MUTE},
+ {SDLK_F1, KEY_F + 1},
+ {SDLK_F2, KEY_F + 2},
+ {SDLK_F3, KEY_F + 3},
+ {SDLK_F4, KEY_F + 4},
+ {SDLK_F5, KEY_F + 5},
+ {SDLK_F6, KEY_F + 6},
+ {SDLK_F7, KEY_F + 7},
+ {SDLK_F8, KEY_F + 8},
+ {SDLK_F9, KEY_F + 9},
+ {SDLK_F10, KEY_F + 10},
+ {SDLK_F11, KEY_F + 11},
+ {SDLK_F12, KEY_F + 12},
+ {SDLK_F13, KEY_F + 13},
+ {SDLK_F14, KEY_F + 14},
+ {SDLK_F15, KEY_F + 15},
+ {SDLK_F16, KEY_F + 16},
+ {SDLK_F17, KEY_F + 17},
+ {SDLK_F18, KEY_F + 18},
+ {SDLK_F19, KEY_F + 19},
+ {SDLK_F20, KEY_F + 20},
+ {SDLK_F21, KEY_F + 21},
+ {SDLK_F22, KEY_F + 22},
+ {SDLK_F23, KEY_F + 23},
+ {SDLK_F24, KEY_F + 24}
+};
+
+struct priv {
+ bool reinit_renderer;
+ SDL_Window *window;
+ SDL_Renderer *renderer;
+ SDL_RendererInfo renderer_info;
+ SDL_Texture *tex;
+ mp_image_t texmpi;
+ mp_image_t *ssmpi;
+ struct mp_rect src_rect;
+ struct mp_rect dst_rect;
+ struct mp_osd_res osd_res;
+ int int_pause;
+ struct formatmap_entry osd_format;
+ struct osd_bitmap_surface {
+ int bitmap_id;
+ int bitmap_pos_id;
+ struct osd_target {
+ SDL_Rect source;
+ SDL_Rect dest;
+ SDL_Texture *tex;
+ SDL_Texture *tex2;
+ } *targets;
+ int num_targets;
+ int targets_size;
+ } osd_surfaces[MAX_OSD_PARTS];
+ unsigned int mouse_timer;
+ int mouse_hidden;
+ int brightness, contrast;
+};
+
+static bool is_good_renderer(int n, const char *driver_name_wanted)
+{
+ SDL_RendererInfo ri;
+ if (SDL_GetRenderDriverInfo(n, &ri))
+ return false;
+
+ if (driver_name_wanted && driver_name_wanted[0])
+ if (strcmp(driver_name_wanted, ri.name))
+ return false;
+
+ int i, j;
+ for (i = 0; i < ri.num_texture_formats; ++i)
+ for (j = 0; j < sizeof(formats) / sizeof(formats[0]); ++j)
+ if (ri.texture_formats[i] == formats[j].sdl)
+ if (formats[j].is_rgba)
+ return true;
+
+ return false;
+}
+
+static void destroy_renderer(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+
+ // free ALL the textures
+ if (vc->tex) {
+ SDL_DestroyTexture(vc->tex);
+ vc->tex = NULL;
+ }
+
+ int i, j;
+ for (i = 0; i < MAX_OSD_PARTS; ++i) {
+ for (j = 0; j < vc->osd_surfaces[i].targets_size; ++j) {
+ if (vc->osd_surfaces[i].targets[j].tex) {
+ SDL_DestroyTexture(vc->osd_surfaces[i].targets[j].tex);
+ vc->osd_surfaces[i].targets[j].tex = NULL;
+ }
+ if (vc->osd_surfaces[i].targets[j].tex2) {
+ SDL_DestroyTexture(vc->osd_surfaces[i].targets[j].tex2);
+ vc->osd_surfaces[i].targets[j].tex2 = NULL;
+ }
+ }
+ }
+
+ if (vc->renderer) {
+ SDL_DestroyRenderer(vc->renderer);
+ vc->renderer = NULL;
+ }
+
+ if (vc->window) {
+ SDL_DestroyWindow(vc->window);
+ vc->window = NULL;
+ }
+}
+
+static int init_renderer(struct vo *vo, int w, int h)
+{
+ struct priv *vc = vo->priv;
+
+ vc->window = SDL_CreateWindow("MPV",
+ SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED,
+ w, h,
+ SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN);
+ if (!vc->window) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[sdl] SDL_CreateWindow failedd\n");
+ destroy_renderer(vo);
+ return -1;
+ }
+
+ int n = SDL_GetNumRenderDrivers();
+ int i;
+ for (i = 0; i < n; ++i)
+ if (is_good_renderer(i, SDL_GetHint(SDL_HINT_RENDER_DRIVER)))
+ break;
+ if (i >= n)
+ for (i = 0; i < n; ++i)
+ if (is_good_renderer(i, NULL))
+ break;
+ if (i >= n) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[sdl] No supported renderer\n");
+ destroy_renderer(vo);
+ return -1;
+ }
+
+ vc->renderer = SDL_CreateRenderer(vc->window, i, 0);
+ if (!vc->renderer) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[sdl] SDL_CreateRenderer failed\n");
+ destroy_renderer(vo);
+ return -1;
+ }
+
+ if (SDL_GetRendererInfo(vc->renderer, &vc->renderer_info)) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[sdl] SDL_GetRendererInfo failed\n");
+ destroy_renderer(vo);
+ return -1;
+ }
+
+ mp_msg(MSGT_VO, MSGL_INFO, "[sdl] Using %s\n", vc->renderer_info.name);
+
+ int j;
+ for (i = 0; i < vc->renderer_info.num_texture_formats; ++i)
+ for (j = 0; j < sizeof(formats) / sizeof(formats[0]); ++j)
+ if (vc->renderer_info.texture_formats[i] == formats[j].sdl)
+ if (formats[j].is_rgba)
+ vc->osd_format = formats[j];
+
+ return 0;
+}
+
+static void resize(struct vo *vo, int w, int h)
+{
+ struct priv *vc = vo->priv;
+ vo->dwidth = w;
+ vo->dheight = h;
+ vo_get_src_dst_rects(vo, &vc->src_rect, &vc->dst_rect,
+ &vc->osd_res);
+ SDL_RenderSetLogicalSize(vc->renderer, w, h);
+ vo->want_redraw = true;
+}
+
+static void force_resize(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ int w, h;
+ SDL_GetWindowSize(vc->window, &w, &h);
+ resize(vo, w, h);
+}
+
+static void check_resize(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ int w, h;
+ SDL_GetWindowSize(vc->window, &w, &h);
+ if (vo->dwidth != w || vo->dheight != h)
+ resize(vo, w, h);
+}
+
+static void set_fullscreen(struct vo *vo, int fs)
+{
+ struct priv *vc = vo->priv;
+ struct MPOpts *opts = vo->opts;
+
+ if (opts->vidmode)
+ SDL_SetWindowDisplayMode(vc->window, NULL);
+ else {
+ SDL_DisplayMode mode;
+ if (!SDL_GetCurrentDisplayMode(SDL_GetWindowDisplay(vc->window), &mode))
+ SDL_SetWindowDisplayMode(vc->window, &mode);
+ }
+
+ if (SDL_SetWindowFullscreen(vc->window, fs)) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[sdl] SDL_SetWindowFullscreen failed\n");
+ return;
+ }
+
+ // toggling fullscreen might recreate the window, so better guard for this
+ SDL_DisableScreenSaver();
+
+ vo_fs = fs;
+ force_resize(vo);
+}
+
+static int config(struct vo *vo, uint32_t width, uint32_t height,
+ uint32_t d_width, uint32_t d_height, uint32_t flags,
+ uint32_t format)
+{
+ struct priv *vc = vo->priv;
+
+ if (vc->reinit_renderer) {
+ destroy_renderer(vo);
+ vc->reinit_renderer = false;
+ }
+
+ if (vc->window)
+ SDL_SetWindowSize(vc->window, d_width, d_height);
+ else {
+ if (init_renderer(vo, d_width, d_height) != 0)
+ return -1;
+ }
+
+ if (vc->tex)
+ SDL_DestroyTexture(vc->tex);
+ Uint32 texfmt = SDL_PIXELFORMAT_UNKNOWN;
+ int i, j;
+ for (i = 0; i < vc->renderer_info.num_texture_formats; ++i)
+ for (j = 0; j < sizeof(formats) / sizeof(formats[0]); ++j)
+ if (vc->renderer_info.texture_formats[i] == formats[j].sdl)
+ if (format == formats[j].mpv)
+ texfmt = formats[j].sdl;
+ if (texfmt == SDL_PIXELFORMAT_UNKNOWN) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[sdl] Invalid pixel format\n");
+ return -1;
+ }
+
+ vc->tex = SDL_CreateTexture(vc->renderer, texfmt,
+ SDL_TEXTUREACCESS_STREAMING, width, height);
+ if (!vc->tex) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[sdl] Could not create a texture\n");
+ return -1;
+ }
+
+ mp_image_t *texmpi = &vc->texmpi;
+ texmpi->width = texmpi->w = width;
+ texmpi->height = texmpi->h = height;
+ mp_image_setfmt(texmpi, format);
+ switch (texmpi->num_planes) {
+ case 1:
+ case 3:
+ break;
+ default:
+ mp_msg(MSGT_VO, MSGL_ERR, "[sdl] Invalid plane count\n");
+ SDL_DestroyTexture(vc->tex);
+ vc->tex = NULL;
+ return -1;
+ }
+
+ vc->ssmpi = alloc_mpi(width, height, format);
+
+ resize(vo, d_width, d_height);
+
+ SDL_DisableScreenSaver();
+
+ if (flags & VOFLAG_FULLSCREEN)
+ set_fullscreen(vo, 1);
+
+ SDL_SetWindowTitle(vc->window, vo_get_window_title(vo));
+
+ SDL_ShowWindow(vc->window);
+
+ check_resize(vo);
+
+ return 0;
+}
+
+static void flip_page(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ SDL_RenderPresent(vc->renderer);
+}
+
+static void check_events(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ struct MPOpts *opts = vo->opts;
+ SDL_Event ev;
+
+ if (opts->cursor_autohide_delay >= 0) {
+ if (!vc->mouse_hidden &&
+ (GetTimerMS() - vc->mouse_timer >= opts->cursor_autohide_delay)) {
+ SDL_ShowCursor(0);
+ vc->mouse_hidden = 1;
+ }
+ } else if (opts->cursor_autohide_delay == -1) {
+ if (vc->mouse_hidden) {
+ SDL_ShowCursor(1);
+ vc->mouse_hidden = 0;
+ }
+ } else if (opts->cursor_autohide_delay == -2) {
+ if (!vc->mouse_hidden) {
+ SDL_ShowCursor(0);
+ vc->mouse_hidden = 1;
+ }
+ }
+
+ while (SDL_PollEvent(&ev)) {
+ switch (ev.type) {
+ case SDL_WINDOWEVENT:
+ switch (ev.window.event) {
+ case SDL_WINDOWEVENT_EXPOSED:
+ vo->want_redraw = true;
+ break;
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ check_resize(vo);
+ break;
+ }
+ break;
+ case SDL_QUIT:
+ mplayer_put_key(vo->key_fifo, KEY_CLOSE_WIN);
+ break;
+ case SDL_TEXTINPUT: {
+ int sdl_mod = SDL_GetModState();
+ int mpv_mod = 0;
+ // we ignore KMOD_LSHIFT, KMOD_RSHIFT and KMOD_RALT because
+ // these are already factored into ev.text.text
+ if (sdl_mod & (KMOD_LCTRL | KMOD_RCTRL))
+ mpv_mod |= KEY_MODIFIER_CTRL;
+ if (sdl_mod & KMOD_LALT)
+ mpv_mod |= KEY_MODIFIER_ALT;
+ if (sdl_mod & (KMOD_LGUI | KMOD_RGUI))
+ mpv_mod |= KEY_MODIFIER_META;
+ struct bstr t = {
+ ev.text.text, strlen(ev.text.text)
+ };
+ mplayer_put_key_utf8(vo->key_fifo, mpv_mod, t);
+ break;
+ }
+ case SDL_KEYDOWN: {
+ // Issue: we don't know in advance whether this keydown event
+ // will ALSO cause a SDL_TEXTINPUT event
+ // So we're conservative, and only map non printable keycodes
+ // (e.g. function keys, arrow keys, etc.)
+ // However, this does lose some keypresses at least on X11
+ // (e.g. Ctrl-A generates SDL_KEYDOWN only, but the key is
+ // 'a'... and 'a' is normally also handled by SDL_TEXTINPUT).
+ // The default config does not use Ctrl, so this is fine...
+ int keycode = 0;
+ int i;
+ for (i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i)
+ if (keys[i].sdl == ev.key.keysym.sym) {
+ keycode = keys[i].mpv;
+ break;
+ }
+ if (keycode) {
+ if (ev.key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT))
+ keycode |= KEY_MODIFIER_SHIFT;
+ if (ev.key.keysym.mod & (KMOD_LCTRL | KMOD_RCTRL))
+ keycode |= KEY_MODIFIER_CTRL;
+ if (ev.key.keysym.mod & (KMOD_LALT | KMOD_RALT))
+ keycode |= KEY_MODIFIER_ALT;
+ if (ev.key.keysym.mod & (KMOD_LGUI | KMOD_RGUI))
+ keycode |= KEY_MODIFIER_META;
+ mplayer_put_key(vo->key_fifo, keycode);
+ }
+ break;
+ }
+ case SDL_MOUSEMOTION:
+ if (opts->cursor_autohide_delay >= 0) {
+ SDL_ShowCursor(1);
+ vc->mouse_hidden = 0;
+ vc->mouse_timer = GetTimerMS();
+ }
+ vo_mouse_movement(vo, ev.motion.x, ev.motion.y);
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ if (opts->cursor_autohide_delay >= 0) {
+ SDL_ShowCursor(1);
+ vc->mouse_hidden = 0;
+ vc->mouse_timer = GetTimerMS();
+ }
+ mplayer_put_key(vo->key_fifo,
+ (MOUSE_BTN0 + ev.button.button - 1) | MP_KEY_DOWN);
+ break;
+ case SDL_MOUSEBUTTONUP:
+ if (opts->cursor_autohide_delay >= 0) {
+ SDL_ShowCursor(1);
+ vc->mouse_hidden = 0;
+ vc->mouse_timer = GetTimerMS();
+ }
+ mplayer_put_key(vo->key_fifo,
+ (MOUSE_BTN0 + ev.button.button - 1));
+ break;
+ case SDL_MOUSEWHEEL:
+ break;
+ }
+ }
+}
+
+static void uninit(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ destroy_renderer(vo);
+ free_mp_image(vc->ssmpi);
+ SDL_QuitSubSystem(SDL_INIT_VIDEO);
+ talloc_free(vc);
+}
+
+static void subbitmap_to_texture(struct vo *vo, SDL_Texture *tex,
+ struct sub_bitmap *bmp,
+ uint32_t andmask, uint32_t ormask)
+{
+ struct priv *vc = vo->priv;
+
+ uint32_t *temppixels;
+ temppixels = talloc_array(vo, uint32_t, bmp->w * bmp->h);
+
+ // apply pixel masks
+ int x, y;
+ for (y = 0; y < bmp->h; ++y) {
+ const uint32_t *src =
+ (const uint32_t *) ((const char *) bmp->bitmap + y * bmp->stride);
+ uint32_t *dst = temppixels + y * bmp->w;
+ for (x = 0; x < bmp->w; ++x)
+ dst[x] = (src[x] & andmask) | ormask;
+ }
+
+ // convert to SDL's format and upload
+ void *pixels;
+ int pitch;
+ if (SDL_LockTexture(tex, NULL, &pixels, &pitch)) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[sdl] Could not lock texture\n");
+ } else {
+ SDL_ConvertPixels(bmp->w, bmp->h, SDL_PIXELFORMAT_ARGB8888,
+ temppixels, sizeof(uint32_t) * bmp->w,
+ vc->osd_format.sdl,
+ pixels, pitch);
+ SDL_UnlockTexture(tex);
+ }
+
+ talloc_free(temppixels);
+}
+
+static void generate_osd_part(struct vo *vo, struct sub_bitmaps *imgs)
+{
+ struct priv *vc = vo->priv;
+ struct osd_bitmap_surface *sfc = &vc->osd_surfaces[imgs->render_index];
+
+ if (imgs->format == SUBBITMAP_EMPTY || imgs->num_parts == 0)
+ return;
+
+ if (imgs->bitmap_pos_id == sfc->bitmap_pos_id)
+ return;
+
+ if (imgs->num_parts > sfc->targets_size) {
+ sfc->targets = talloc_realloc(vc, sfc->targets,
+ struct osd_target, imgs->num_parts);
+ memset(&sfc->targets[sfc->targets_size], 0, sizeof(struct osd_target) *
+ (imgs->num_parts - sfc->targets_size));
+ sfc->targets_size = imgs->num_parts;
+ }
+ sfc->num_targets = imgs->num_parts;
+
+ for (int i = 0; i < imgs->num_parts; i++) {
+ struct osd_target *target = sfc->targets + i;
+ struct sub_bitmap *bmp = imgs->parts + i;
+
+ target->source = (SDL_Rect){
+ 0, 0, bmp->w, bmp->h
+ };
+ target->dest = (SDL_Rect){
+ bmp->x, bmp->y, bmp->dw, bmp->dh
+ };
+
+ if (imgs->bitmap_id != sfc->bitmap_id || !target->tex) {
+ // tex: alpha blended texture
+ if (target->tex) {
+ SDL_DestroyTexture(target->tex);
+ target->tex = NULL;
+ }
+ if (!target->tex)
+ target->tex = SDL_CreateTexture(vc->renderer,
+ vc->osd_format.sdl, SDL_TEXTUREACCESS_STREAMING,
+ bmp->w, bmp->h);
+ if (!target->tex) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[sdl] Could not create texture\n");
+ }
+ if (target->tex) {
+ SDL_SetTextureBlendMode(target->tex,
+ SDL_BLENDMODE_BLEND);
+ subbitmap_to_texture(vo, target->tex, bmp,
+ 0xFF000000, 0x00000000); // RGBA -> 000A
+ }
+
+ // tex2: added texture
+ if (target->tex2) {
+ SDL_DestroyTexture(target->tex2);
+ target->tex2 = NULL;
+ }
+ if (!target->tex2)
+ target->tex2 = SDL_CreateTexture(vc->renderer,
+ vc->osd_format.sdl, SDL_TEXTUREACCESS_STREAMING,
+ bmp->w, bmp->h);
+ if (!target->tex2) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[sdl] Could not create texture\n");
+ }
+ if (target->tex2) {
+ SDL_SetTextureBlendMode(target->tex2,
+ SDL_BLENDMODE_ADD);
+ subbitmap_to_texture(vo, target->tex2, bmp,
+ 0x00FFFFFF, 0xFF000000); // RGBA -> RGB1
+ }
+ }
+ }
+
+ sfc->bitmap_id = imgs->bitmap_id;
+ sfc->bitmap_pos_id = imgs->bitmap_pos_id;
+}
+
+static void draw_osd_part(struct vo *vo, int index)
+{
+ struct priv *vc = vo->priv;
+ struct osd_bitmap_surface *sfc = &vc->osd_surfaces[index];
+ int i;
+
+ for (i = 0; i < sfc->num_targets; i++) {
+ struct osd_target *target = sfc->targets + i;
+ if (target->tex)
+ SDL_RenderCopy(vc->renderer, target->tex,
+ &target->source, &target->dest);
+ if (target->tex2)
+ SDL_RenderCopy(vc->renderer, target->tex2,
+ &target->source, &target->dest);
+ }
+}
+
+static void draw_osd_cb(void *ctx, struct sub_bitmaps *imgs)
+{
+ struct vo *vo = ctx;
+ generate_osd_part(vo, imgs);
+ draw_osd_part(vo, imgs->render_index);
+}
+
+static void draw_osd(struct vo *vo, struct osd_state *osd)
+{
+ struct priv *vc = vo->priv;
+
+ static const bool formats[SUBBITMAP_COUNT] = {
+ [SUBBITMAP_RGBA] = true,
+ };
+
+ osd_draw(osd, vc->osd_res, osd->vo_pts, 0, formats, draw_osd_cb, vo);
+}
+
+static int preinit(struct vo *vo, const char *arg)
+{
+ struct priv *vc = vo->priv;
+
+ if (SDL_WasInit(SDL_INIT_VIDEO)) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[sdl] already initialized\n");
+ return -1;
+ }
+
+ // work around broken XVidMode support for now (TODO regularily test
+ // whether this is still necessary)
+ SDL_SetHintWithPriority(SDL_HINT_VIDEO_X11_XVIDMODE, "0",
+ SDL_HINT_DEFAULT);
+
+ // predefine SDL defaults (SDL env vars shall override)
+ SDL_SetHintWithPriority(SDL_HINT_RENDER_SCALE_QUALITY, "1",
+ SDL_HINT_DEFAULT);
+
+ // predefine MPV options (SDL env vars shall be overridden)
+ if (vo_vsync)
+ SDL_SetHintWithPriority(SDL_HINT_RENDER_VSYNC, "1",
+ SDL_HINT_OVERRIDE);
+ else
+ SDL_SetHintWithPriority(SDL_HINT_RENDER_VSYNC, "0",
+ SDL_HINT_OVERRIDE);
+
+ if (SDL_InitSubSystem(SDL_INIT_VIDEO)) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[sdl] SDL_Init failed\n");
+ return -1;
+ }
+
+ // try creating a renderer (this also gets the renderer_info data
+ // for query_format to use!)
+ if (init_renderer(vo, 640, 480) != 0)
+ return -1;
+
+ // please reinitialize the renderer to proper size on config()
+ vc->reinit_renderer = true;
+
+ // we don't have proper event handling
+ vo->wakeup_period = 0.02;
+
+ // initialize the autohide timer properly
+ vc->mouse_timer = GetTimerMS();
+
+ return 0;
+}
+
+static int query_format(struct vo *vo, uint32_t format)
+{
+ struct priv *vc = vo->priv;
+ int i, j;
+ int cap = VFCAP_CSP_SUPPORTED | VFCAP_FLIP | VFCAP_ACCEPT_STRIDE |
+ VFCAP_OSD;
+ for (i = 0; i < vc->renderer_info.num_texture_formats; ++i)
+ for (j = 0; j < sizeof(formats) / sizeof(formats[0]); ++j)
+ if (vc->renderer_info.texture_formats[i] == formats[j].sdl)
+ if (format == formats[j].mpv)
+ return cap;
+ return 0;
+}
+
+static void draw_image(struct vo *vo, mp_image_t *mpi, double pts)
+{
+ struct priv *vc = vo->priv;
+ void *pixels;
+ int pitch;
+
+ // decode brightness/contrast
+ int color_add = 0;
+ int color_mod = 255;
+ int brightness = vc->brightness;
+ int contrast = vc->contrast;
+
+ // only in this range it is possible to do brightness/contrast control
+ // properly, using just additive render operations and color modding
+ // (SDL2 provides no subtractive rendering, sorry)
+ if (2 * brightness < contrast) {
+ //brightness = (brightness + 2 * contrast) / 5; // closest point
+ brightness = (brightness + contrast) / 3; // equal adjustment
+ contrast = 2 * brightness;
+ }
+
+ // convert to values SDL2 likes
+ color_mod = ((contrast + 100) * 255 + 50) / 100;
+ color_add = ((2 * brightness - contrast) * 255 + 100) / 200;
+
+ // clamp
+ if (color_mod < 0)
+ color_mod = 0;
+ if (color_mod > 255)
+ color_mod = 255;
+ // color_add can't be < 0
+ if (color_add > 255)
+ color_add = 255;
+
+ // typically this runs in parallel with the following copy_mpi call
+ SDL_SetRenderDrawColor(vc->renderer, color_add, color_add, color_add, 255);
+ SDL_RenderClear(vc->renderer);
+
+ // use additive blending for the video texture only if the clear color is
+ // not black (faster especially for the software renderer)
+ if (color_add)
+ SDL_SetTextureBlendMode(vc->tex, SDL_BLENDMODE_ADD);
+ else
+ SDL_SetTextureBlendMode(vc->tex, SDL_BLENDMODE_NONE);
+
+ if (mpi) {
+ if (SDL_LockTexture(vc->tex, NULL, &pixels, &pitch)) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[sdl] SDL_LockTexture failed\n");
+ return;
+ }
+
+ mp_image_t *texmpi = &vc->texmpi;
+ texmpi->planes[0] = pixels;
+ texmpi->stride[0] = pitch;
+ if (texmpi->num_planes == 3) {
+ if (texmpi->imgfmt == IMGFMT_YV12) {
+ texmpi->planes[2] =
+ ((Uint8 *) texmpi->planes[0] + texmpi->h * pitch);
+ texmpi->stride[2] = pitch / 2;
+ texmpi->planes[1] =
+ ((Uint8 *) texmpi->planes[2] + (texmpi->h * pitch) / 4);
+ texmpi->stride[1] = pitch / 2;
+ } else {
+ texmpi->planes[1] =
+ ((Uint8 *) texmpi->planes[0] + texmpi->h * pitch);
+ texmpi->stride[1] = pitch / 2;
+ texmpi->planes[2] =
+ ((Uint8 *) texmpi->planes[1] + (texmpi->h * pitch) / 4);
+ texmpi->stride[2] = pitch / 2;
+ }
+ }
+ copy_mpi(texmpi, mpi);
+
+ SDL_UnlockTexture(vc->tex);
+ }
+
+ SDL_Rect src, dst;
+ src.x = vc->src_rect.x0;
+ src.y = vc->src_rect.y0;
+ src.w = vc->src_rect.x1 - vc->src_rect.x0;
+ src.h = vc->src_rect.y1 - vc->src_rect.y0;
+ dst.x = vc->dst_rect.x0;
+ dst.y = vc->dst_rect.y0;
+ dst.w = vc->dst_rect.x1 - vc->dst_rect.x0;
+ dst.h = vc->dst_rect.y1 - vc->dst_rect.y0;
+
+ // typically this runs in parallel with the following copy_mpi call
+ if (color_mod > 255) {
+ SDL_SetTextureColorMod(vc->tex, color_mod / 2, color_mod / 2, color_mod / 2);
+ SDL_RenderCopy(vc->renderer, vc->tex, &src, &dst);
+ SDL_RenderCopy(vc->renderer, vc->tex, &src, &dst);
+ } else {
+ SDL_SetTextureColorMod(vc->tex, color_mod, color_mod, color_mod);
+ SDL_RenderCopy(vc->renderer, vc->tex, &src, &dst);
+ }
+ if (mpi)
+ copy_mpi(vc->ssmpi, mpi);
+}
+
+static void update_screeninfo(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ SDL_DisplayMode mode;
+ if (SDL_GetCurrentDisplayMode(SDL_GetWindowDisplay(vc->window), &mode)) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[sdl] SDL_GetCurrentDisplayMode failed\n");
+ return;
+ }
+ struct MPOpts *opts = vo->opts;
+ opts->vo_screenwidth = mode.w;
+ opts->vo_screenheight = mode.h;
+ aspect_save_screenres(vo, opts->vo_screenwidth, opts->vo_screenheight);
+}
+
+static struct mp_image *get_screenshot(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ mp_image_t *image = alloc_mpi(vc->ssmpi->width, vc->ssmpi->height,
+ vc->ssmpi->imgfmt);
+ copy_mpi(image, vc->ssmpi);
+ return image;
+}
+
+static struct mp_image *get_window_screenshot(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ mp_image_t *image = alloc_mpi(vo->dwidth, vo->dheight, vc->osd_format.mpv);
+ if (SDL_RenderReadPixels(vc->renderer, NULL, vc->osd_format.sdl,
+ image->planes[0], image->stride[0])) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[sdl] SDL_RenderReadPixels failed\n");
+ free_mp_image(image);
+ return NULL;
+ }
+ return image;
+}
+
+static int set_eq(struct vo *vo, const char *name, int value)
+{
+ struct priv *vc = vo->priv;
+
+ if (!strcasecmp(name, "brightness"))
+ vc->brightness = value;
+ else if (!strcasecmp(name, "contrast"))
+ vc->contrast = value;
+ else
+ return VO_NOTIMPL;
+
+ vo->want_redraw = true;
+
+ return VO_TRUE;
+}
+
+static int get_eq(struct vo *vo, const char *name, int *value)
+{
+ struct priv *vc = vo->priv;
+
+ if (!strcasecmp(name, "brightness"))
+ *value = vc->brightness;
+ else if (!strcasecmp(name, "contrast"))
+ *value = vc->contrast;
+ else
+ return VO_NOTIMPL;
+
+ return VO_TRUE;
+}
+
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ struct priv *vc = vo->priv;
+ switch (request) {
+ case VOCTRL_QUERY_FORMAT:
+ return query_format(vo, *((uint32_t *)data));
+ case VOCTRL_DRAW_IMAGE:
+ draw_image(vo, (mp_image_t *)data, vo->next_pts);
+ return 0;
+ case VOCTRL_FULLSCREEN:
+ set_fullscreen(vo, !vo_fs);
+ return 1;
+ case VOCTRL_PAUSE:
+ return vc->int_pause = 1;
+ case VOCTRL_RESUME:
+ return vc->int_pause = 0;
+ case VOCTRL_REDRAW_FRAME:
+ draw_image(vo, NULL, MP_NOPTS_VALUE);
+ return 1;
+ case VOCTRL_UPDATE_SCREENINFO:
+ update_screeninfo(vo);
+ return 1;
+ case VOCTRL_GET_PANSCAN:
+ return VO_TRUE;
+ case VOCTRL_SET_PANSCAN:
+ force_resize(vo);
+ return VO_TRUE;
+ case VOCTRL_SET_EQUALIZER: {
+ struct voctrl_set_equalizer_args *args = data;
+ return set_eq(vo, args->name, args->value);
+ }
+ case VOCTRL_GET_EQUALIZER: {
+ struct voctrl_get_equalizer_args *args = data;
+ return get_eq(vo, args->name, args->valueptr);
+ }
+ case VOCTRL_SCREENSHOT: {
+ struct voctrl_screenshot_args *args = data;
+ if (args->full_window)
+ args->out_image = get_window_screenshot(vo);
+ else
+ args->out_image = get_screenshot(vo);
+ return true;
+ }
+ }
+ return VO_NOTIMPL;
+}
+
+#undef OPT_BASE_STRUCT
+#define OPT_BASE_STRUCT struct priv
+
+const struct vo_driver video_out_sdl = {
+ .is_new = true,
+ .info = &(const vo_info_t) {
+ "SDL 2.0 Renderer",
+ "sdl",
+ "Rudolf Polzer <divVerent@xonotic.org>",
+ ""
+ },
+ .priv_size = sizeof(struct priv),
+ .preinit = preinit,
+ .config = config,
+ .control = control,
+ .uninit = uninit,
+ .check_events = check_events,
+ .draw_osd = draw_osd,
+ .flip_page = flip_page,
+};