Hello ports@,
Here's a second try at adding a working audio backend to Godot. It took
me some time but now I think it's ready. It was fun, and even if I
didn't knew anything about audio handling, I have learned something.
The sio_open manpage in particular was incredibly helpful at explaining
the various concept.
You can find attached a patch to add a sndio driver. I'm also taking up
the maintainership as previously discussed off-list with Thomas.
Riccardo (cc'd) and me have been testing this for a week. We've tested
with Oddventure, Hive Time and some hobby projects. Oddventure crashes
regularly at some point (after ~ 15-20 minutes of gameplay), but it does
so regardless of the sndio patch. As a test, I've been able to play for
roughly 25-30 minutes at Hive Time with SIO_ERROR^1 set without having
xruns, so I'm quite confident the driver isn't utterly broken.
As Thomas suggested, it would be nice to upstream this patch but as I'm
not able to compile and run the engine from the master branch I'm
refraining to for the moment. In the meantime, it would be nice to have
this tested by a wider audience and, possibly, also committed :)
Some notes (and questions) about the patch:
- it's better to bundle the code in ${FILESDIR} and copy it in
post-extract (what I'm currently doing) or have patches against
/dev/null? I think the first option is more clear
- the $OpenBSD$ cvs tag on the sources is correct? portcheck doesn't
complain, but I've never done this before so I'm not sure.
- the driver only implements the minimum necessary in order to play
sounds: other audio driver also let the user change the device and
some driver (e.g. pulseaudio) also support recording audio. Adding
support for recording shouldn't be difficult, but I don't know a
single game that requires this. Switching audio device at runtime
doesn't seem too much useful to me, but I think I can implement it
eventually
- (I'm a noob when it comes to audio stuff so apologies) it's better
SIO_SYNC or SIO_IGNORE for a game? SIO_IGNORE makes the device
pauses during overruns and underruns, while SIO_SYNC makes the device
discard the samples on overrun and then play the same amount of
silence later.
- in the main loop in AudioDriverSndio::thread_func I'm not checking
the return of sio_open/sio_close: this is deliberate. It's to keep
the code simple, as if an error occurs during the open/close we'll
notice it eventually some lines after on sio_write and exit there.
- while other driver fills sample_in with zeroes when there is nothing
to play, doing so with sndio would make the whole engine sluggish.
The was_active + sio_open/close dance is due to that. I don't have
an explanation for that, and I've got the idea from the sndio
website[2]
- (please remember that I don't have any prior experience with audio
stuff) every other audio driver has a loop like
for ( ... ) {
ad->samples_out.write[i] = ad->samples_in[i] >> 16;
}
I don't know why it's needed to shift every sample but, hey, everyone
is doing it and so am I :)
Sorry for the long email and, as always, comments are more than welcome!
[1]: SIO_ERROR makes sndio terminate on overrun. If you want to try,
you can decomment the line with ``par.xrun = SIO_ERROR;'' in
audio_driver_sndio.cpp
[2]: http://www.sndio.org/tips.html#section_7
Index: Makefile
===================================================================
RCS file: /cvs/ports/games/godot/Makefile,v
retrieving revision 1.14
diff -u -p -r1.14 Makefile
--- Makefile 18 Aug 2020 17:39:30 -0000 1.14
+++ Makefile 24 Aug 2020 17:59:45 -0000
@@ -6,9 +6,9 @@ V = 3.2.2
DISTNAME = godot-${V}-stable
PKGNAME = godot-${V}
CATEGORIES = games
-REVISION = 0
+REVISION = 1
HOMEPAGE = https://godotengine.org/
-MAINTAINER = Thomas Frohwein <[email protected]>
+MAINTAINER = Omar Polo <[email protected]>
# MIT
PERMIT_PACKAGE = Yes
@@ -16,7 +16,7 @@ PERMIT_PACKAGE = Yes
WANTLIB += ${COMPILER_LIBCXX}
WANTLIB += GL X11 Xau Xcursor Xdmcp Xext Xfixes Xi Xinerama Xrandr
WANTLIB += Xrender c enet execinfo freetype intl m mbedtls mbedcrypto
-WANTLIB += mbedx509 mpcdec ogg opus opusfile png theora theoradec
+WANTLIB += mbedx509 mpcdec ogg opus opusfile png sndio theora theoradec
WANTLIB += vorbis vorbisfile webp xcb z pcre2-32 vpx zstd
COMPILER = base-clang ports-gcc base-gcc
@@ -72,6 +72,8 @@ LIB_DEPENDS = archivers/zstd \
NO_TEST = Yes
+LDFLAGS += -lsndio
+
.if ${MACHINE_ARCH:Mhppa}
LDFLAGS += -latomic
WANTLIB += atomic
@@ -83,6 +85,9 @@ CFLAGS += -mlongcall
CXXFLAGS += -mlongcall
LDFLAGS += -Wl,--relax
.endif
+
+post-extract:
+ cp -R ${FILESDIR}/sndio ${WRKDIST}/drivers
pre-configure:
${SUBST_CMD} ${WRKSRC}/drivers/unix/os_unix.cpp
Index: files/sndio/SCsub
===================================================================
RCS file: files/sndio/SCsub
diff -N files/sndio/SCsub
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ files/sndio/SCsub 24 Aug 2020 17:59:45 -0000
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+# $OpenBSD$
+
+Import('env')
+
+env.add_source_files(env.drivers_sources, "*.cpp")
Index: files/sndio/audio_driver_sndio.cpp
===================================================================
RCS file: files/sndio/audio_driver_sndio.cpp
diff -N files/sndio/audio_driver_sndio.cpp
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ files/sndio/audio_driver_sndio.cpp 24 Aug 2020 17:59:45 -0000
@@ -0,0 +1,181 @@
+/* $OpenBSD$ */
+
+#include "audio_driver_sndio.h"
+
+#ifdef SNDIO_ENABLED
+
+#include "core/os/os.h"
+#include "core/project_settings.h"
+
+#include <poll.h>
+#include <stdio.h>
+
+Error AudioDriverSndio::init() {
+ active = false;
+ thread_exited = false;
+ exit_thread = false;
+ speaker_mode = SPEAKER_MODE_STEREO;
+ was_active = 0;
+
+ handle = sio_open(SIO_DEVANY, SIO_PLAY, 0);
+ ERR_FAIL_COND_V(handle == NULL, ERR_CANT_OPEN);
+
+ struct sio_par par;
+ sio_initpar(&par);
+
+ par.bits = 16;
+ par.rate = GLOBAL_GET("audio/mix_rate");
+
+ /*
+ * XXX: require SIO_SYNC instead of SIO_IGNORE (the default) ?
+ * what is the most sensible choice for a game?
+ */
+ // par.xrun = SIO_ERROR;
+
+ if (!sio_setpar(handle, &par))
+ ERR_FAIL_COND_V(1, ERR_CANT_OPEN);
+
+ if (!sio_getpar(handle, &par))
+ ERR_FAIL_COND_V(1, ERR_CANT_OPEN);
+
+ mix_rate = par.rate;
+ channels = par.pchan;
+ period_size = par.appbufsz;
+
+ samples_in.resize(period_size * channels);
+ samples_out.resize(period_size * channels);
+
+ mutex = Mutex::create();
+ thread = Thread::create(AudioDriverSndio::thread_func, this);
+
+ return OK;
+}
+
+void AudioDriverSndio::thread_func(void *p_udata) {
+ AudioDriverSndio *ad = (AudioDriverSndio*)p_udata;
+
+ int nfds = sio_nfds(ad->handle);
+ struct pollfd *pfds;
+ if ((pfds = (struct pollfd*)calloc(sizeof(*pfds), nfds)) == NULL) {
+ ERR_PRINTS("cannot allocate memory");
+ ad->active = false;
+ ad->exit_thread = true;
+ ad->thread_exited = true;
+ return;
+ }
+
+ while (!ad->exit_thread) {
+ if (ad->was_active) {
+ nfds = sio_pollfd(ad->handle, pfds, POLLOUT);
+ if (nfds > 0) {
+ if (poll(pfds, nfds, -1) < 0) {
+ ERR_PRINTS("sndio: poll failed");
+ ad->exit_thread = true;
+ break;
+ }
+ }
+ }
+
+ ad->lock();
+ ad->start_counting_ticks();
+
+ if (!ad->active) {
+ if (ad->was_active) {
+ ad->was_active = 0;
+ sio_stop(ad->handle);
+ }
+
+ ad->stop_counting_ticks();
+ ad->unlock();
+ /* XXX: sleep a bit here? */
+ continue;
+ } else {
+ if (!ad->was_active) {
+ ad->was_active = 1;
+ sio_start(ad->handle);
+ }
+
+ ad->audio_server_process(ad->period_size,
ad->samples_in.ptrw());
+
+ for (size_t i = 0; i < ad->period_size*ad->channels;
++i) {
+ ad->samples_out.write[i] = ad->samples_in[i] >>
16;
+ }
+ }
+
+ size_t left = ad->period_size * ad->channels * sizeof(int16_t);
+ size_t wrote = 0;
+
+ while (left != 0 && !ad->exit_thread) {
+ const uint8_t *src = (const
uint8_t*)ad->samples_out.ptr();
+ size_t w = sio_write(ad->handle, (void*)(src + wrote),
left);
+
+ if (w == 0 && sio_eof(ad->handle)) {
+ ERR_PRINTS("sndio: fatal error");
+ ad->exit_thread = true;
+ } else {
+ wrote += w;
+ left -= w;
+ }
+ }
+
+ ad->stop_counting_ticks();
+ ad->unlock();
+ }
+
+ free((void*)pfds);
+ ad->thread_exited = true;
+}
+
+void AudioDriverSndio::start() {
+ active = true;
+}
+
+int AudioDriverSndio::get_mix_rate() const {
+ return mix_rate;
+}
+
+AudioDriver::SpeakerMode AudioDriverSndio::get_speaker_mode() const {
+ return speaker_mode;
+}
+
+void AudioDriverSndio::lock() {
+ if (!thread || !mutex)
+ return;
+ mutex->lock();
+}
+
+void AudioDriverSndio::unlock() {
+ if (!thread || !mutex)
+ return;
+ mutex->unlock();
+}
+
+void AudioDriverSndio::finish() {
+ if (thread) {
+ exit_thread = true;
+ Thread::wait_to_finish(thread);
+
+ memdelete(thread);
+ thread = NULL;
+ }
+
+ if (mutex) {
+ memdelete(mutex);
+ mutex = NULL;
+ }
+
+ if (handle) {
+ sio_close(handle);
+ handle = NULL;
+ }
+}
+
+AudioDriverSndio::AudioDriverSndio() {
+ mutex = NULL;
+ thread = NULL;
+}
+
+AudioDriverSndio::~AudioDriverSndio() {
+}
+
+#endif
Index: files/sndio/audio_driver_sndio.h
===================================================================
RCS file: files/sndio/audio_driver_sndio.h
diff -N files/sndio/audio_driver_sndio.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ files/sndio/audio_driver_sndio.h 24 Aug 2020 17:59:45 -0000
@@ -0,0 +1,49 @@
+/* $OpenBSD$ */
+
+#include "servers/audio_server.h"
+
+#ifdef SNDIO_ENABLED
+
+#include "core/os/mutex.h"
+#include "core/os/thread.h"
+
+#include <sndio.h>
+
+class AudioDriverSndio : public AudioDriver {
+ Thread *thread;
+ Mutex *mutex;
+
+ Vector<int32_t> samples_in;
+ Vector<int16_t> samples_out;
+ int was_active;
+
+ struct sio_hdl *handle;
+
+ static void thread_func(void*);
+ size_t period_size;
+
+ unsigned int mix_rate;
+ int channels;
+ bool active;
+ bool thread_exited;
+ mutable bool exit_thread;
+ SpeakerMode speaker_mode;
+
+public:
+ const char *get_name() const {
+ return "sndio";
+ }
+
+ virtual Error init();
+ virtual void start();
+ virtual int get_mix_rate() const;
+ virtual SpeakerMode get_speaker_mode() const;
+ virtual void lock();
+ virtual void unlock();
+ virtual void finish();
+
+ AudioDriverSndio();
+ ~AudioDriverSndio();
+};
+
+#endif
Index: patches/patch-drivers_SCsub
===================================================================
RCS file: patches/patch-drivers_SCsub
diff -N patches/patch-drivers_SCsub
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-drivers_SCsub 24 Aug 2020 17:59:45 -0000
@@ -0,0 +1,15 @@
+$OpenBSD$
+
+add sndio
+
+Index: drivers/SCsub
+--- drivers/SCsub.orig
++++ drivers/SCsub
+@@ -12,6 +12,7 @@ SConscript("windows/SCsub")
+ SConscript("alsa/SCsub")
+ SConscript("coreaudio/SCsub")
+ SConscript("pulseaudio/SCsub")
++SConscript("sndio/SCsub")
+ if env["platform"] == "windows":
+ SConscript("wasapi/SCsub")
+ if env["xaudio2"]:
Index: patches/patch-platform_x11_detect_py
===================================================================
RCS file: /cvs/ports/games/godot/patches/patch-platform_x11_detect_py,v
retrieving revision 1.2
diff -u -p -r1.2 patch-platform_x11_detect_py
--- patches/patch-platform_x11_detect_py 19 Jul 2020 13:02:38 -0000
1.2
+++ patches/patch-platform_x11_detect_py 24 Aug 2020 17:59:45 -0000
@@ -1,6 +1,6 @@
$OpenBSD: patch-platform_x11_detect_py,v 1.2 2020/07/19 13:02:38 thfr Exp $
-remove hardcoded -O2, found by bcallah@
+remove hardcoded -O2, found by bcallah@. Add sndio
Index: platform/x11/detect.py
--- platform/x11/detect.py.orig
@@ -27,3 +27,14 @@ Index: platform/x11/detect.py
env.Prepend(CPPDEFINES=["DEBUG_ENABLED"])
if env["debug_symbols"] == "yes":
+@@ -302,6 +293,10 @@ def configure(env):
+ env.ParseConfig("pkg-config alsa --libs")
+ else:
+ print("ALSA libraries not found, disabling driver")
++
++ print("Enabling sndio")
++ env.Append(CPPDEFINES=["SNDIO_ENABLED"])
++ env.Append(LDFLAGS=["-lsndio"])
+
+ if env["pulseaudio"]:
+ if os.system("pkg-config --exists libpulse") == 0: # 0 means found
Index: patches/patch-platform_x11_os_x11_cpp
===================================================================
RCS file: /cvs/ports/games/godot/patches/patch-platform_x11_os_x11_cpp,v
retrieving revision 1.2
diff -u -p -r1.2 patch-platform_x11_os_x11_cpp
--- patches/patch-platform_x11_os_x11_cpp 19 Jul 2020 13:02:38 -0000
1.2
+++ patches/patch-platform_x11_os_x11_cpp 24 Aug 2020 17:59:45 -0000
@@ -1,6 +1,6 @@
$OpenBSD: patch-platform_x11_os_x11_cpp,v 1.2 2020/07/19 13:02:38 thfr Exp $
-fix libXrandr library name
+fix libXrandr library name and load sndio
Index: platform/x11/os_x11.cpp
--- platform/x11/os_x11.cpp.orig
@@ -18,3 +18,14 @@ Index: platform/x11/os_x11.cpp
} else {
XRRQueryVersion(x11_display, &xrandr_major, &xrandr_minor);
if (((xrandr_major << 8) | xrandr_minor) >= 0x0105) {
+@@ -3596,6 +3596,10 @@ void OS_X11::update_real_mouse_position() {
+ }
+
+ OS_X11::OS_X11() {
++
++#ifdef SNDIO_ENABLED
++ AudioDriverManager::add_driver(&driver_sndio);
++#endif
+
+ #ifdef PULSEAUDIO_ENABLED
+ AudioDriverManager::add_driver(&driver_pulseaudio);
Index: patches/patch-platform_x11_os_x11_h
===================================================================
RCS file: patches/patch-platform_x11_os_x11_h
diff -N patches/patch-platform_x11_os_x11_h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-platform_x11_os_x11_h 24 Aug 2020 17:59:45 -0000
@@ -0,0 +1,26 @@
+$OpenBSD$
+
+load sndio
+
+Index: platform/x11/os_x11.h
+--- platform/x11/os_x11.h.orig
++++ platform/x11/os_x11.h
+@@ -36,6 +36,7 @@
+ #include "crash_handler_x11.h"
+ #include "drivers/alsa/audio_driver_alsa.h"
+ #include "drivers/alsamidi/midi_driver_alsamidi.h"
++#include "drivers/sndio/audio_driver_sndio.h"
+ #include "drivers/pulseaudio/audio_driver_pulseaudio.h"
+ #include "drivers/unix/os_unix.h"
+ #include "joypad_linux.h"
+@@ -185,6 +186,10 @@ class OS_X11 : public OS_Unix {
+
+ #ifdef ALSAMIDI_ENABLED
+ MIDIDriverALSAMidi driver_alsamidi;
++#endif
++
++#ifdef SNDIO_ENABLED
++ AudioDriverSndio driver_sndio;
+ #endif
+
+ #ifdef PULSEAUDIO_ENABLED