Dear Maintainer
Here's a patch with pulseaudio support (applied last). I made it with AI
and don't understand exactly what's it's doing, so don't bother to ask
me. I did my best to avoid the AI weirdness. I did design decisions, the
AI wasn't able to do it on it's own. I'm amazed that it works.
On a high level, it has a thin wrapper as the expected sound interface
of the library and a pulseaudio manager class with a thread as the
backend. I split it this way so that there's only one pulseaudio
managing object and avoid synchronization and blocking writes issues.
it works on my slow laptop, maybe on a faster machine other
synchronization bugs will show up.
the target application for the fix is tuxkart, the ancestor of supertuxkart.
You can easily build it with makedeb on the mpr (it's like the AUR but
for debs)
https://mpr.makedeb.org/packages/tuxkart
i'm not sure how exactly to package the lib the debian way, i'm using
this to run configure. I didn't touch the building process, it requires
the pulseaudio flags to use the patch
CXXFLAGS="-fPIC -DSL_USE_PULSEAUDIO" CFLAGS="-fPIC -DSL_USE_PULSEAUDIO"
LDFLAGS="-fPIC -lpulse-simple -lpulse -lpthread" ./configure --prefix=/usr
On 12/08/2023 01:32, michel wrote:
Package: libplib1
Version: 1.8.5-14+b1
Severity: normal
X-Debbugs-Cc: okgomdjgbm...@gmail.com
Dear Maintainer,
Debian is maintaining this right? Upsteam seams dead.
I recently compiled tuxkart (uses libplib), not supertuxkart, tuxkart. It is
the ancestor of supertuxkart.
I was amazed to find out that it actually worked O_O (2006) . The only problem,
is that it compiled with OSS support. And it's a version that is not compatible
with the simple OSS emulations that just load a library, but require full OSS
emulation with a fake /dev/dsp. Trying to emulate that is especially
inefficient and buggy.
the problem is in the SL library inside libplib. It would require to be updated
with pulseaudio.
Asuming this is not a wontfix.
From 233ae06b69c4e19ae526a3ba27558f3b8dc432bd Mon Sep 17 00:00:00 2001
From: pirate486743186 <429925+pirate486743...@users.noreply.github.com>
Date: Tue, 24 Jun 2025 23:53:38 +0200
Subject: [PATCH] pulseaudio
---
src/sl/sl.h | 79 +++++++++-
src/sl/slDSP.cxx | 320 +++++++++++++++++++++++++++++++++++++++++
src/sl/slPortability.h | 13 +-
3 files changed, 410 insertions(+), 2 deletions(-)
diff --git a/src/sl/sl.h b/src/sl/sl.h
index 65325eb..eea1924 100644
--- a/src/sl/sl.h
+++ b/src/sl/sl.h
@@ -28,8 +28,16 @@
#include <stdio.h>
#include "slPortability.h"
+#ifdef SL_USING_PULSEAUDIO
+#include <pulse/simple.h>
+#include <pulse/error.h>
+#include <pthread.h>
+#endif
+
#ifdef SL_USING_OSS_AUDIO
#define SLDSP_DEFAULT_DEVICE "/dev/dsp"
+#elif defined(SL_USING_PULSEAUDIO)
+#define SLDSP_DEFAULT_DEVICE "pulse" // PulseAudio doesn't use device files
#elif defined(UL_WIN32)
#define SLDSP_DEFAULT_DEVICE "dsp"
#elif defined(UL_BSD)
@@ -63,6 +71,62 @@ class slEnvelope ;
class slScheduler ;
class slDSP ;
+#ifdef SL_USING_PULSEAUDIO
+class PulseAudioThreadManager {
+private:
+ static PulseAudioThreadManager* instance;
+ static pthread_mutex_t instance_mutex;
+ static pthread_mutex_t ref_mutex;
+ static int ref_count;
+
+ pa_simple* pa_stream;
+ pa_sample_spec pa_spec;
+ int pa_error;
+
+ enum { PA_BUFFER_SIZE = 4096 }; // 4 KB (1 chunk of 4096)
+ char pa_ringbuf[PA_BUFFER_SIZE];
+ size_t pa_ringbuf_read;
+ size_t pa_ringbuf_write;
+ size_t pa_ringbuf_fill;
+
+ pthread_t pa_thread;
+ pthread_mutex_t pa_mutex;
+ pthread_cond_t pa_cond;
+ pthread_cond_t pa_cond_space_avail;
+ int pa_thread_exit;
+
+ int initialized;
+ int rate;
+ int stereo;
+ int bps;
+
+ PulseAudioThreadManager();
+ ~PulseAudioThreadManager();
+
+ static void* pa_audio_thread_func(void* arg);
+
+public:
+ static PulseAudioThreadManager* getInstance();
+ void addRef();
+ void release();
+
+ int initialize(int _rate, int _stereo, int _bps);
+ void shutdown();
+ int writeAudio(const void* buffer, size_t length);
+ void sync();
+ void stop();
+
+ size_t getBufferFill();
+ size_t getBufferSize() const { return PA_BUFFER_SIZE; }
+
+ int getRate() const { return rate; }
+ int getStereo() const { return stereo; }
+ int getBps() const { return bps; }
+ int isInitialized() const { return initialized; }
+ int hasError() const { return pa_error != 0; }
+};
+#endif
+
extern const char *__slPendingError ;
class slDSP
@@ -119,8 +183,13 @@ private:
#endif
+#if defined(SL_USING_PULSEAUDIO)
+ // PulseAudio interface - uses PulseAudioThreadManager internally
+ PulseAudioThreadManager* pa_manager;
+ long counter; // counter-written packets (like other implementations)
+#endif
-#if !defined(UL_WIN32) && !defined(UL_MACINTOSH) && !defined(UL_MAC_OSX)
+#if !defined(UL_WIN32) && !defined(UL_MACINTOSH) && !defined(UL_MAC_OSX) && !defined(SL_USING_PULSEAUDIO)
int ioctl ( int cmd, int param = 0 )
{
if ( error ) return param ;
@@ -166,13 +235,21 @@ public:
slDSP ( int _rate = SL_DEFAULT_SAMPLING_RATE,
int _stereo = SL_FALSE, int _bps = 8 )
{
+#if defined(SL_USING_PULSEAUDIO)
+ open ( NULL, _rate, _stereo, _bps ) ;
+#else
open ( SLDSP_DEFAULT_DEVICE, _rate, _stereo, _bps ) ;
+#endif
}
slDSP ( const char *device, int _rate = SL_DEFAULT_SAMPLING_RATE,
int _stereo = SL_FALSE, int _bps = 8 )
{
+#if defined(SL_USING_PULSEAUDIO)
open ( device, _rate, _stereo, _bps ) ;
+#else
+ open ( device, _rate, _stereo, _bps ) ;
+#endif
}
~slDSP () { close () ; }
diff --git a/src/sl/slDSP.cxx b/src/sl/slDSP.cxx
index 223c929..98cd7ac 100644
--- a/src/sl/slDSP.cxx
+++ b/src/sl/slDSP.cxx
@@ -25,6 +25,11 @@
#include "sl.h"
#include <errno.h>
+#ifdef SL_USING_PULSEAUDIO
+#include <pulse/simple.h>
+#include <pulse/error.h>
+#endif
+
static int init_bytes ;
#ifdef SL_USING_OSS_AUDIO
@@ -1082,4 +1087,319 @@ float slDSP::secondsUsed ()
#endif
+#ifdef SL_USING_PULSEAUDIO
+
+/* ------------------------------------------------------------ */
+/* PULSEAUDIO - Linux with PulseAudio */
+/* ------------------------------------------------------------ */
+
+void slDSP::open ( const char *device, int _rate, int _stereo, int _bps )
+{
+ counter = 0;
+ pa_manager = PulseAudioThreadManager::getInstance();
+ pa_manager->addRef();
+ if (pa_manager->initialize(_rate, _stereo, _bps) < 0) {
+ fprintf(stderr, "slDSP: PulseAudio initialization failed\n");
+ error = SL_TRUE;
+ stereo = SL_FALSE;
+ bps = 8;
+ rate = 8000;
+ } else {
+ error = SL_FALSE;
+ stereo = _stereo;
+ bps = _bps;
+ rate = _rate;
+ }
+}
+
+void slDSP::close ()
+{
+ if (pa_manager) {
+ pa_manager->release();
+ pa_manager = NULL;
+ }
+}
+
+void slDSP::write ( void *buffer, size_t length )
+{
+ if (error || !pa_manager || length == 0)
+ return;
+ if (pa_manager->writeAudio(buffer, length) < 0) {
+ fprintf(stderr, "slDSP: PulseAudio write failed\n");
+ error = SL_TRUE;
+ } else {
+ counter += length;
+ }
+}
+
+void slDSP::getBufferInfo ()
+{
+ /* Not implemented for PulseAudio */
+}
+
+int slDSP::getDriverBufferSize ()
+{
+ return 4096;
+}
+
+float slDSP::secondsRemaining ()
+{
+ if (error || !pa_manager) return 0.0f;
+ size_t ring_fill = pa_manager->getBufferFill();
+ size_t ring_size = pa_manager->getBufferSize();
+ size_t remaining_in_ring = (ring_size > ring_fill) ? (ring_size - ring_fill) : 0;
+ float pipeline_headroom = 0.05f;
+ float bytes_per_sec = (float)(rate * (stereo ? 2 : 1) * (bps / 8));
+ if (bytes_per_sec == 0) return 0.0f;
+ float ring_remaining_sec = (float)remaining_in_ring / bytes_per_sec;
+ float result = ring_remaining_sec + pipeline_headroom;
+ return result;
+}
+
+float slDSP::secondsUsed ()
+{
+ if (error || !pa_manager) return 0.0f;
+ size_t ring_fill = pa_manager->getBufferFill();
+ float bytes_per_sec = (float)(rate * (stereo ? 2 : 1) * (bps / 8));
+ if (bytes_per_sec == 0) return 0.0f;
+ float pipeline_offset = 0.01f;
+ float ring_fill_sec = (float)ring_fill / bytes_per_sec;
+ float result = ring_fill_sec + pipeline_offset;
+ return result;
+}
+
+void slDSP::sync ()
+{
+ if (pa_manager) pa_manager->sync();
+}
+
+void slDSP::stop ()
+{
+ /* Not implemented for PulseAudio */
+}
+
+// PulseAudioThreadManager implementation
+PulseAudioThreadManager* PulseAudioThreadManager::instance = NULL;
+pthread_mutex_t PulseAudioThreadManager::instance_mutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_mutex_t PulseAudioThreadManager::ref_mutex = PTHREAD_MUTEX_INITIALIZER;
+int PulseAudioThreadManager::ref_count = 0;
+
+PulseAudioThreadManager* PulseAudioThreadManager::getInstance() {
+ if (instance == NULL) {
+ pthread_mutex_lock(&instance_mutex);
+ if (instance == NULL) {
+ instance = new PulseAudioThreadManager();
+ }
+ pthread_mutex_unlock(&instance_mutex);
+ }
+ return instance;
+}
+
+void PulseAudioThreadManager::addRef() {
+ pthread_mutex_lock(&ref_mutex);
+ ref_count++;
+ pthread_mutex_unlock(&ref_mutex);
+}
+
+void PulseAudioThreadManager::release() {
+ pthread_mutex_lock(&ref_mutex);
+ ref_count--;
+ if (ref_count <= 0) {
+ pthread_mutex_unlock(&ref_mutex);
+ pthread_mutex_lock(&instance_mutex);
+ if (instance != NULL) {
+ delete instance;
+ instance = NULL;
+ }
+ pthread_mutex_unlock(&instance_mutex);
+ } else {
+ pthread_mutex_unlock(&ref_mutex);
+ }
+}
+
+PulseAudioThreadManager::PulseAudioThreadManager()
+ : pa_stream(NULL), pa_error(0), pa_ringbuf_read(0), pa_ringbuf_write(0),
+ pa_ringbuf_fill(0), pa_thread_exit(0), initialized(0), rate(0), stereo(0), bps(0) {
+
+ pthread_mutex_init(&pa_mutex, NULL);
+ pthread_cond_init(&pa_cond, NULL);
+ pthread_cond_init(&pa_cond_space_avail, NULL);
+}
+
+PulseAudioThreadManager::~PulseAudioThreadManager() {
+ shutdown();
+ pthread_mutex_destroy(&pa_mutex);
+ pthread_cond_destroy(&pa_cond);
+ pthread_cond_destroy(&pa_cond_space_avail);
+}
+
+int PulseAudioThreadManager::initialize(int _rate, int _stereo, int _bps) {
+ if (initialized) return 0;
+
+ rate = _rate;
+ stereo = _stereo;
+ bps = _bps;
+
+ pa_spec.format = (bps == 16) ? PA_SAMPLE_S16LE : PA_SAMPLE_U8;
+ pa_spec.rate = rate;
+ pa_spec.channels = stereo ? 2 : 1;
+
+ pa_error = 0;
+ pa_stream = pa_simple_new(
+ NULL, // Use default server
+ "plib", // Application name
+ PA_STREAM_PLAYBACK,
+ NULL, // Use default device
+ "playback",
+ &pa_spec,
+ NULL, // Use default channel map
+ NULL, // Use default buffering attributes
+ &pa_error
+ );
+
+ if (!pa_stream) {
+ fprintf(stderr, "PulseAudioThreadManager: initialization failed: %s\n", pa_strerror(pa_error));
+ return -1;
+ }
+
+ // Initialize ring buffer
+ pa_ringbuf_read = 0;
+ pa_ringbuf_write = 0;
+ pa_ringbuf_fill = 0;
+
+ // Start audio thread
+ pa_thread_exit = 0;
+ if (pthread_create(&pa_thread, NULL, pa_audio_thread_func, this) != 0) {
+ fprintf(stderr, "PulseAudioThreadManager: failed to create audio thread\n");
+ pa_simple_free(pa_stream);
+ pa_stream = NULL;
+ return -1;
+ }
+
+ initialized = 1;
+ return 0;
+}
+
+void PulseAudioThreadManager::shutdown() {
+ if (!initialized) return;
+
+ // Signal thread to exit
+ pthread_mutex_lock(&pa_mutex);
+ pa_thread_exit = 1;
+ pthread_cond_signal(&pa_cond);
+ pthread_mutex_unlock(&pa_mutex);
+
+ // Wait for thread to finish
+ pthread_join(pa_thread, NULL);
+
+ // Clean up PulseAudio
+ if (pa_stream) {
+ pa_simple_free(pa_stream);
+ pa_stream = NULL;
+ }
+
+ initialized = 0;
+}
+int PulseAudioThreadManager::writeAudio(const void* buffer, size_t length) {
+ if (!initialized || !buffer || length == 0) return -1;
+
+ pthread_mutex_lock(&pa_mutex);
+
+ // Wait for space in ring buffer
+ while (pa_ringbuf_fill + length > PA_BUFFER_SIZE && !pa_thread_exit) {
+ pthread_cond_wait(&pa_cond_space_avail, &pa_mutex);
+ }
+
+ if (pa_thread_exit) {
+ pthread_mutex_unlock(&pa_mutex);
+ return -1;
+ }
+
+ // Copy data to ring buffer
+ size_t bytes_to_write = length;
+ const char* src = (const char*)buffer;
+
+ while (bytes_to_write > 0) {
+ size_t space_until_end = PA_BUFFER_SIZE - pa_ringbuf_write;
+ size_t chunk_size = (bytes_to_write < space_until_end) ? bytes_to_write : space_until_end;
+
+ memcpy(&pa_ringbuf[pa_ringbuf_write], src, chunk_size);
+
+ pa_ringbuf_write = (pa_ringbuf_write + chunk_size) % PA_BUFFER_SIZE;
+ pa_ringbuf_fill += chunk_size;
+
+ src += chunk_size;
+ bytes_to_write -= chunk_size;
+ }
+
+ // Signal audio thread that data is available
+ pthread_cond_signal(&pa_cond);
+ pthread_mutex_unlock(&pa_mutex);
+
+ return 0;
+}
+
+void* PulseAudioThreadManager::pa_audio_thread_func(void* arg) {
+ PulseAudioThreadManager* manager = (PulseAudioThreadManager*)arg;
+ while (1) {
+ size_t bytes_to_write;
+ size_t read_ptr;
+ pthread_mutex_lock(&manager->pa_mutex);
+ while (manager->pa_ringbuf_fill == 0 && !manager->pa_thread_exit) {
+ pthread_cond_wait(&manager->pa_cond, &manager->pa_mutex);
+ }
+ if (manager->pa_thread_exit) {
+ pthread_mutex_unlock(&manager->pa_mutex);
+ break;
+ }
+ bytes_to_write = manager->pa_ringbuf_fill;
+ read_ptr = manager->pa_ringbuf_read;
+ pthread_mutex_unlock(&manager->pa_mutex);
+ if (bytes_to_write > 0) {
+ size_t space_until_end = PA_BUFFER_SIZE - read_ptr;
+ if (bytes_to_write > space_until_end) {
+ if (pa_simple_write(manager->pa_stream, &manager->pa_ringbuf[read_ptr], space_until_end, &manager->pa_error) < 0) {
+ fprintf(stderr, "PulseAudioThreadManager: write failed (chunk 1): %s\n", pa_strerror(manager->pa_error));
+ break;
+ }
+ if (pa_simple_write(manager->pa_stream, &manager->pa_ringbuf[0], bytes_to_write - space_until_end, &manager->pa_error) < 0) {
+ fprintf(stderr, "PulseAudioThreadManager: write failed (chunk 2): %s\n", pa_strerror(manager->pa_error));
+ break;
+ }
+ } else {
+ if (pa_simple_write(manager->pa_stream, &manager->pa_ringbuf[read_ptr], bytes_to_write, &manager->pa_error) < 0) {
+ fprintf(stderr, "PulseAudioThreadManager: write failed (contiguous): %s\n", pa_strerror(manager->pa_error));
+ break;
+ }
+ }
+ pthread_mutex_lock(&manager->pa_mutex);
+ manager->pa_ringbuf_read = (read_ptr + bytes_to_write) % PA_BUFFER_SIZE;
+ manager->pa_ringbuf_fill -= bytes_to_write;
+ pthread_cond_signal(&manager->pa_cond_space_avail);
+ pthread_mutex_unlock(&manager->pa_mutex);
+ }
+ }
+ return NULL;
+}
+
+void PulseAudioThreadManager::sync() {
+ if (pa_stream) {
+ pa_simple_drain(pa_stream, NULL);
+ }
+}
+
+void PulseAudioThreadManager::stop() {
+ if (pa_stream) {
+ pa_simple_flush(pa_stream, NULL);
+ }
+}
+
+size_t PulseAudioThreadManager::getBufferFill() {
+ pthread_mutex_lock(&pa_mutex);
+ size_t fill = pa_ringbuf_fill;
+ pthread_mutex_unlock(&pa_mutex);
+ return fill;
+}
+
+#endif
diff --git a/src/sl/slPortability.h b/src/sl/slPortability.h
index b29adc0..1e1972b 100644
--- a/src/sl/slPortability.h
+++ b/src/sl/slPortability.h
@@ -51,7 +51,7 @@
#include <limits.h>
#include <math.h>
-#if (defined(UL_LINUX) || defined(UL_BSD) || defined(UL_GNU)) && !defined(__NetBSD__)
+#if (defined(UL_LINUX) || defined(UL_BSD) || defined(UL_GNU)) && !defined(__NetBSD__) && !defined(SL_USE_PULSEAUDIO)
#define SL_USING_OSS_AUDIO 1
#endif
@@ -91,5 +91,16 @@
# include <sys/stropts.h>
#endif
+// PulseAudio support for Linux
+#if defined(UL_LINUX) && defined(SL_USE_PULSEAUDIO)
+#define SL_USING_PULSEAUDIO 1
+#endif
+
+#ifdef SL_USING_PULSEAUDIO
+// Requires libpulse-dev (Debian/Ubuntu) or pulseaudio-libs-devel (Fedora)
+#include <pulse/simple.h>
+#include <pulse/error.h>
+#endif
+
#endif
--
2.39.5