From af6c4b4982bbd531c711d5b84cf6bab6a6cfd6bc Mon Sep 17 00:00:00 2001
From: Sergey Fedorov <barracuda@macos-powerpc.org>
Date: Sun, 8 Jun 2025 15:50:51 +0800
Subject: [PATCH 09/12] Revert breaking 650a48d, fixes portaudio

Reverts https://github.com/DeaDBeeF-Player/deadbeef/commit/650a48d911a7ffaed8f9300674d34d9bb208dd5f
---
 plugins/portaudio/Makefile.am |   4 +-
 plugins/portaudio/portaudio.c | 562 ++++++++++++++++++----------------
 2 files changed, 304 insertions(+), 262 deletions(-)

diff --git a/plugins/portaudio/Makefile.am b/plugins/portaudio/Makefile.am
index 9a686a0b7..89b7e20e0 100644
--- a/plugins/portaudio/Makefile.am
+++ b/plugins/portaudio/Makefile.am
@@ -2,6 +2,6 @@ if HAVE_PORTAUDIO
 pkglib_LTLIBRARIES = portaudio.la
 portaudio_la_CFLAGS = $(CFLAGS) $(PORTAUDIO_DEPS_CFLAGS) -std=gnu99 -I@top_srcdir@/include
 portaudio_la_SOURCES = portaudio.c
-portaudio_la_LDFLAGS = -module -avoid-version -lportaudio $(DISPATCH_LIBS)
-portaudio_la_LIBADD = $(LDADD) $(PORTAUDIO_DEPS_LIBS) $(DISPATCH_CFLAGS)
+portaudio_la_LDFLAGS = -module -avoid-version -lportaudio
+portaudio_la_LIBADD = $(LDADD) $(PORTAUDIO_DEPS_LIBS)
 endif
diff --git a/plugins/portaudio/portaudio.c b/plugins/portaudio/portaudio.c
index d6c3d03e5..fa545c697 100644
--- a/plugins/portaudio/portaudio.c
+++ b/plugins/portaudio/portaudio.c
@@ -1,6 +1,6 @@
 /*
     Portaudio output plugin for DeaDBeeF Player
-    Copyright (C) 2017-2023 Jakub Wasylków
+    Copyright (C) 2017 Jakub Wasylków
 
     This software is provided 'as-is', without any express or implied
     warranty.  In no event will the authors be held liable for any damages
@@ -20,6 +20,7 @@
 
     3. This notice may not be removed or altered from any source distribution.
 */
+
 #include <stdint.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -31,7 +32,6 @@
 
 #include <deadbeef/deadbeef.h>
 #include <portaudio.h>
-#include <dispatch/dispatch.h>
 
 #ifdef __MINGW32__
 #include "windows.h"
@@ -47,7 +47,6 @@
 #define info(...) { deadbeef->log_detailed (&plugin.plugin, 1, __VA_ARGS__); }
 
 // This is my 4th rewrite of portaudio plugin. Ok, actually 5th, because idea in 4th rewrite didn't work.
-// Now it's 6th...
 
 static PaSampleFormat
 pa_GetSampleFormat (int bps, int is_float);
@@ -56,7 +55,7 @@ static void
 pa_SetDefault ();
 
 static void
-portaudio_stream_create (void);
+portaudio_stream_start (void);
 
 static void
 portaudio_thread (void *context);
@@ -90,248 +89,280 @@ static void
 pa_stream_finished_callback (void *uData);
 
 static DB_output_t plugin;
-static DB_functions_t *deadbeef;
-static ddb_playback_state_t state;
-
-// Queues
-
-// stream queue: create_stream, destroy_stream or change_parameters
-static dispatch_queue_t stream_queue;
-// command queue: play/pause/stop
-static dispatch_queue_t command_queue;
+DB_functions_t *deadbeef;
+static uintptr_t mutex;
+static intptr_t portaudio_tid;
 
-// Semaphores
-
-// block when stream is on allowing only one stream
-static dispatch_semaphore_t stream_semaphore;
-// block when there is no stream, stopping commands to run
-static dispatch_semaphore_t command_semaphore;
+static ddb_playback_state_t state;
 
-// current stream
+// actual stream
 static PaStream *stream = 0;
-static PaStreamParameters stream_parameters;
-static int stream_force_reset = 0;
-static int stream_framesize = 0;
-static int stream_buffersize = 0;
-// data for callback
-static int stream_totalsize = 0;
-static int stream_lastframecount = 0;
 // we use plugin.fmt as fmt information
-
+static PaStreamParameters stream_parameters;
+// stream control
+static struct uData * userData;
 // requested stream
 static ddb_waveformat_t requested_fmt;
 
-// portaudio_stream_create opens a stream using stream_parameters and plugin.fmt information
-static void
-portaudio_stream_create (void) {
-    trace ("> portaudio_stream_start\n");
-    dispatch_async(stream_queue, ^{
-        trace ("portaudio_stream_start\n");
-        dispatch_semaphore_wait(stream_semaphore, DISPATCH_TIME_FOREVER);
-        // Soundcard
-        {
-            deadbeef->conf_lock ();
-            const char * portaudio_soundcard_string = deadbeef->conf_get_str_fast ("portaudio_soundcard", "default");
-            if (strcmp(portaudio_soundcard_string, "default") == 0) {
-                stream_parameters.device = Pa_GetDefaultOutputDevice ();
-                deadbeef->conf_unlock ();
-            }
-            else {
-                deadbeef->conf_unlock ();
-                stream_parameters.device = deadbeef->conf_get_int ("portaudio_soundcard", -1);
-            }
-        }
+#define STREAMS_TO_CLOSE_MAX 15
+#define LOOP_CLOSE 10
+// streams to be closed in callback later
+static PaStream * streams_to_close[STREAMS_TO_CLOSE_MAX] = {NULL, NULL};
+
+
+#define P_UDATA(x) (*((struct uData *) x))
+
+#define STREAM_CONTINUE 0
+#define STREAM_COMPLETE 1
+#define STREAM_ABORT 2
+
+struct uData {
+    // tells callback to finish stream (through STREAM_* definitions)
+    int abort;
+    // 'thread' number which can help with debugging
+    int num;
+    // loop number
+    int i;
+    // stream uData belongs to
+    PaStream *stream;
+    // adds stream to terminate queue when uData reaches pa_stream_finished_callback
+    unsigned char terminate;
+    // size of the frame
+    int framesize;
+    // buffer size
+    unsigned long buffer_size;
+};
 
-        // Bufer
-        // Using paFramesPerBufferUnspecified with alsa gives warnings about underrun
-        unsigned long buffer_size;
-        int buffer_size_config = deadbeef->conf_get_int ("portaudio.buffer", DEFAULT_BUFFER_SIZE);
-        if (buffer_size_config == -1)
-            buffer_size = paFramesPerBufferUnspecified;
-        else
-            buffer_size = buffer_size_config;
-
-        // Create stream
-        trace ("portaudio_stream_create: Device: %d, Buffer size: %d\n", stream_parameters.device, buffer_size);
-        PaError err;
-        err = Pa_OpenStream (       &stream,                        // stream pointer
-                                    NULL,                           // inputParameters
-                                    &stream_parameters,             // outputParameters
-                                    plugin.fmt.samplerate,          // sampleRate
-                                    buffer_size,                    // framesPerBuffer
-                                    paNoFlag,                       // flags
-                                    portaudio_callback,             // callback
-                                    NULL);                          // userData
+unsigned char num_assign = 0;
 
-        if (err != paNoError) {
-            trace("Failed to open stream. %s\n", Pa_GetErrorText(err));
-            dispatch_semaphore_signal(stream_semaphore);
-            return;
-        }
+// portaudio_stream_start opens a stream using stream_parameters and plugin.fmt information
+static void
+portaudio_stream_start (void) {
+    //trace ("portaudio_stream_start\n");
 
-        // Stream extra parameters
-        stream_framesize =  plugin.fmt.channels*plugin.fmt.bps/8;
+    deadbeef->mutex_lock (mutex);
 
-        // Continue playback if format changed
-        if (state == DDB_PLAYBACK_STATE_PLAYING) {
-            trace ("portaudio_stream_create: continuing stream\n");
-            PaError err;
-            err = Pa_StartStream(stream);
-            if (err != paNoError) {
-                warn ("Failed to start stream. %s\n", Pa_GetErrorText(err));
-            }
+    // Use default device if none selected
+    {
+        deadbeef->conf_lock ();
+        const char * portaudio_soundcard_string = deadbeef->conf_get_str_fast ("portaudio_soundcard", "default");
+        if (strcmp(portaudio_soundcard_string, "default") == 0) {
+            stream_parameters.device = Pa_GetDefaultOutputDevice ();
+            deadbeef->conf_unlock ();
+        }
+        else {
+            deadbeef->conf_unlock ();
+            stream_parameters.device = deadbeef->conf_get_int ("portaudio_soundcard", -1);
         }
+    }
+    static struct uData * uData;
+    uData = calloc(1,sizeof(struct uData));
+    uData->num = num_assign++;
+    userData = uData;
+
+    // Using paFramesPerBufferUnspecified with alsa gives warnings about underrun
+    int buffer_size_config = deadbeef->conf_get_int ("portaudio.buffer", DEFAULT_BUFFER_SIZE);
+    if (buffer_size_config == -1)
+        uData->buffer_size = paFramesPerBufferUnspecified;
+    else
+        uData->buffer_size = buffer_size_config;
+
+    trace ("portaudio_stream_start [%d]: buffer size %lu\n", uData->num, uData->buffer_size);
+    /* Open an audio I/O stream. */
+    PaError err;
+    err = Pa_OpenStream (       &stream,                        // stream pointer
+                                NULL,                           // inputParameters
+                                &stream_parameters,             // outputParameters
+                                plugin.fmt.samplerate,          // sampleRate
+                                uData->buffer_size,             // framesPerBuffer
+                                paNoFlag,                       // flags
+                                portaudio_callback,             // callback
+                                uData);                         // userData
+
+    if (err != paNoError) {
+        trace("Failed to open stream. %s\n", Pa_GetErrorText(err));
+        deadbeef->mutex_unlock (mutex);
+        return;
+    }
+    uData->stream = stream;
+    uData->framesize =  plugin.fmt.channels*plugin.fmt.bps/8;
+    err = Pa_SetStreamFinishedCallback (stream, pa_stream_finished_callback);
+    if (err != paNoError) {
+        trace ("Failed to set stream finished callback. %s\n", Pa_GetErrorText(err));
+    }
+    deadbeef->mutex_unlock (mutex);
 
-        // Accept commands from now
-        dispatch_semaphore_signal(command_semaphore);
-    });
     return;
 }
 
 static void
-portaudio_stream_destroy (void) {
-    trace ("> portaudio_stream_destroy\n");
-    dispatch_async(stream_queue, ^{
-        if (stream) {
-            trace ("portaudio_stream_destroy: closing stream\n");
-            dispatch_semaphore_wait(command_semaphore, DISPATCH_TIME_NOW);
-            PaError err;
-            if (Pa_IsStreamActive(stream)) {
-                err = Pa_StopStream(stream);
-                if (err != paNoError) {
-                    warn ("Failed to stop stream. %s\n", Pa_GetErrorText(err));
-                }
+pa_stream_finished_callback (void *uData) {
+    trace ("pa_stream_finished_callback %x\n",uData); 
+    if (P_UDATA(uData).terminate) {
+        int i;
+        for (i = 0; i < STREAMS_TO_CLOSE_MAX; i++) {
+            if (i+1 == STREAMS_TO_CLOSE_MAX) {
+                // flush list
+                warn ("pa_stream_finished_callback: streams_to_close full, flushing\n");
+                portaudio_tid = deadbeef->thread_start (portaudio_thread, NULL);
+                while (streams_to_close[0] != NULL)
+                    usleep (20000);
+                i = 0;
             }
-            err = Pa_CloseStream (stream);
-            if (err != paNoError) {
-                warn ("Failed to close stream. %s\n", Pa_GetErrorText(err));
-            }
-            // modify stream parameters, so that new stream will be created in setformat
-            plugin.fmt.bps = 0;
-            stream_totalsize = -1;
-            stream_framesize = -1;
-            stream_lastframecount = -1;
-
-            // Allow new stream
-            stream = 0;
-            dispatch_semaphore_signal(stream_semaphore);
+            if (streams_to_close[i] != NULL)
+                continue;
+            trace ("settings stream to close on pos %d\n",i);
+            streams_to_close[i++] = P_UDATA(uData).stream;
+            streams_to_close[i] = NULL;
+            break;
         }
-        else {
-            // portaudio_free() and portaudio_setformat() combo might try to free stream multiple times
+        free (uData);
+        return;
+    }
+}
+
+static void
+portaudio_thread (void *context) {
+    int i;
+    for (i = 0; i < STREAMS_TO_CLOSE_MAX; i++) {
+        if (streams_to_close[i] == NULL)
+            break;
+        trace ("portaudio_thread: closing stream No. %d\n", i);
+        PaError err;
+        err = Pa_CloseStream (streams_to_close[i]);
+        if (err != paNoError) {
+            trace ("Failed to close stream. %s\n", Pa_GetErrorText(err));
         }
-    });
+    }
+    memset (&streams_to_close, 0, STREAMS_TO_CLOSE_MAX * sizeof(PaStream *));
     return;
 }
 
 static int
 portaudio_init (void) {
+    // this function is for now left for no reason
     trace ("portaudio_init\n");
+    //Pa_Sleep (1000);
     return 0;
 }
 
 // since we can't change stream parameters, we have to abort actual stream, set values and start streaming again
 static int
 portaudio_setformat (ddb_waveformat_t *fmt) {
-    trace("> portaudio_setformat\n");
-    // "Empty"/broken track detection
-    if (!fmt->channels || !fmt->bps) {
-        return -1;
-    }
-    // Enqueue format switch and save new fmt under requested_fmt
     memcpy (&requested_fmt, fmt, sizeof (ddb_waveformat_t));
-    dispatch_async (stream_queue, ^{
-        ddb_waveformat_t *fmt = &requested_fmt;
-        trace ("portaudio_setformat %dbit %s %dch %dHz channelmask=%X\n", requested_fmt.bps, fmt->is_float ? "float" : "int", fmt->channels, fmt->samplerate, fmt->channelmask);
-
-        if (!memcmp (&requested_fmt, &plugin.fmt, sizeof (ddb_waveformat_t)) && !stream_force_reset) {
-            trace ("portaudio_setformat ignored\n");
-            return;
-        }
-        else {
-            stream_force_reset = 0;
-            trace ("switching format: (requested->actual)\n"
-            "bps %d -> %d\n"
-            "is_float %d -> %d\n"
-            "channels %d -> %d\n"
-            "samplerate %d -> %d\n"
-            "channelmask %d -> %d\n"
-            , fmt->bps, plugin.fmt.bps
-            , fmt->is_float, plugin.fmt.is_float
-            , fmt->channels, plugin.fmt.channels
-            , fmt->samplerate, plugin.fmt.samplerate
-            , fmt->channelmask, plugin.fmt.channelmask
-            );
-        }
-
-        // Stop stream
-        portaudio_stream_destroy();
+    trace ("portaudio_setformat %dbit %s %dch %dHz channelmask=%X\n", requested_fmt.bps, fmt->is_float ? "float" : "int", fmt->channels, fmt->samplerate, fmt->channelmask);
 
-        // Change format of the stream
-        dispatch_async (stream_queue, ^{
-            memcpy (&plugin.fmt, &requested_fmt, sizeof (ddb_waveformat_t));
+    if (!memcmp (&requested_fmt, &plugin.fmt, sizeof (ddb_waveformat_t))) {
+        trace ("portaudio_setformat ignored\n");
+        return 0;
+    }
+    else {
+        trace ("switching format: (requested->actual)\n"
+        "bps %d -> %d\n"
+        "is_float %d -> %d\n"
+        "channels %d -> %d\n"
+        "samplerate %d -> %d\n"
+        "channelmask %d -> %d\n"
+        , fmt->bps, plugin.fmt.bps
+        , fmt->is_float, plugin.fmt.is_float
+        , fmt->channels, plugin.fmt.channels
+        , fmt->samplerate, plugin.fmt.samplerate
+        , fmt->channelmask, plugin.fmt.channelmask
+        );
+    }
 
-            // Set new values for new stream
-            // TODO: get which device was requested?
-            PaError err;
-            stream_parameters.device = Pa_GetDefaultOutputDevice ();
-            stream_parameters.channelCount = plugin.fmt.channels;
-            stream_parameters.sampleFormat = pa_GetSampleFormat (plugin.fmt.bps,plugin.fmt.is_float);
-            stream_parameters.suggestedLatency = 0.0;
-            stream_parameters.hostApiSpecificStreamInfo = NULL;
+    // Tell ongoing thread to abort stream (if any)
+    if (userData) {
+        trace ("portaudio_setformat: abort [%d]\n",userData->num);
+        // Pa_StopStream takes too much time to do.
+        //Pa_StopStream (stream);
+        userData->terminate = 1;
+        userData->abort = STREAM_COMPLETE;
+        userData = 0;
+    }
 
-            err = Pa_IsFormatSupported (NULL, &stream_parameters, plugin.fmt.samplerate);
-            if (err != paNoError) {
-                warn ("Unsupported format. %s\n", Pa_GetErrorText(err));
-                // even if it failed -- continue
-            }
-        });
+    memcpy (&plugin.fmt, &requested_fmt, sizeof (ddb_waveformat_t));
+    
+    // Set new values for new stream
+    // TODO: get which device was requested?
+    PaError err;
+    stream_parameters.device = Pa_GetDefaultOutputDevice ();
+    stream_parameters.channelCount = plugin.fmt.channels;
+    stream_parameters.sampleFormat = pa_GetSampleFormat (plugin.fmt.bps,plugin.fmt.is_float);
+    stream_parameters.suggestedLatency = 0.0;
+    stream_parameters.hostApiSpecificStreamInfo = NULL;
+    
+    err = Pa_IsFormatSupported (NULL, &stream_parameters, plugin.fmt.samplerate);
+    if (err != paNoError) {
+        trace ("Unsupported format. %s\n", Pa_GetErrorText(err));
+        // even if it failed -- continue
+    }
 
-        // Start new stream
-        portaudio_stream_create();
-    });
+    // start new stream if was playing before
+    if (stream) {
+        stream = 0;
+        portaudio_stream_start ();
+        err = Pa_StartStream (stream);
+        if (err != paNoError) {
+            trace ("Failed to start stream. %s\n", Pa_GetErrorText(err));
+            state = DDB_PLAYBACK_STATE_STOPPED;
+            return -1;
+        }
+        trace ("portaudio_setformat: Started stream.\n");
+        state = DDB_PLAYBACK_STATE_PLAYING;
+    }
     return 0;
 }
 
 static int
 portaudio_free (void) {
-    // called when plugin changes or with DB_EV_REINIT_SOUND event
-    trace("> portaudio_free\n");
+    // called when plugin changes
+    trace("portaudio_free\n");
     if (stream) {
-        portaudio_stream_destroy();
+        /*if (userData)
+            userData->abort = STREAM_ABORT;
+        */
+        PaError err;
+        err = Pa_AbortStream (stream);
+        if (err != paNoError) {
+            trace ("Failed to abort stream. %s\n", Pa_GetErrorText(err));
+        }
+        trace ("portaudio_free: closing stream No. %d\n", userData->num);
+        err = Pa_CloseStream (stream);
+        stream = 0;
+        if (err != paNoError) {
+            trace ("Failed to close stream. %s\n", Pa_GetErrorText(err));
+        }
     }
     return 0;
 }
 
 static int
 portaudio_play (void) {
-    trace ("> portaudio_play\n");
-
-    // New plugin structure always keeps at least one stream open (unless plugin not in usage)
-    dispatch_async (command_queue, ^{
-        trace ("portaudio_play\n");
-        dispatch_semaphore_wait(command_semaphore, DISPATCH_TIME_FOREVER);
-        state = DDB_PLAYBACK_STATE_PLAYING;
-        if (!Pa_IsStreamActive(stream)) {
-            PaError err;
-            err = Pa_StartStream (stream);
-            if (err != paNoError) {
-                trace ("Failed to start stream. %s\n", Pa_GetErrorText(err));
-                state = DDB_PLAYBACK_STATE_STOPPED;
-            }
-            trace ("portaudio_play: Resumed stream.\n");
-        }
-        else {
-            trace("portaudio_play: Stream already started?\n");
+    trace ("portaudio_play\n");
+    if (!stream) {
+        trace ("portaudio_play: opening stream\n");
+        portaudio_stream_start ();
+    }
+    state = DDB_PLAYBACK_STATE_PLAYING;
+    userData->abort = STREAM_CONTINUE;
+    if (!Pa_IsStreamActive(stream)) {
+        PaError err;
+        err = Pa_StartStream (stream);
+        if (err != paNoError) {
+            trace ("Failed to start stream. %s\n", Pa_GetErrorText(err));
+            state = DDB_PLAYBACK_STATE_STOPPED;
+            return -1;
         }
-        dispatch_semaphore_signal(command_semaphore);
-    });
+        trace ("portaudio_play: Started stream.\n");
+    }
     return 0;
 }
 
-static PaSampleFormat pa_GSFerr () { warn ("portaudio: Sample format wrong? Using Int16.\n"); return paInt16; }
+static PaSampleFormat pa_GSFerr () { warn ("portaudio: Sample format wrong? Using Int16.\n"); return 0; }
 static PaSampleFormat
 pa_GetSampleFormat (int bps, int is_float) {
-    return bps ==  8 ?  paInt8    :
+    return bps ==  8 ?  paUInt8   :
            bps == 16 ?  paInt16   :
            bps == 24 ?  paInt24   :
            bps == 32 && is_float  ? paFloat32 :
@@ -341,22 +372,20 @@ pa_GetSampleFormat (int bps, int is_float) {
 
 static int
 portaudio_stop (void) {
-    trace ("> portaudio_stop\n");
+    trace ("portaudio_stop\n");
     if (state == DDB_PLAYBACK_STATE_STOPPED) {
         return -1;
     }
-    dispatch_async(command_queue, ^{
-        trace ("portaudio_stop\n");
-        dispatch_semaphore_wait(command_semaphore, DISPATCH_TIME_FOREVER);
+    if (stream) {
         PaError err;
         if (!Pa_IsStreamStopped(stream)) {
             err = Pa_AbortStream (stream);
             if (err != paNoError) {
                 trace ("Failed to abort stream. %s\n", Pa_GetErrorText(err));
+                return -1;
             }
         }
-        dispatch_semaphore_signal(command_semaphore);
-    });
+    }
     state = DDB_PLAYBACK_STATE_STOPPED;
     deadbeef->streamer_reset (1);
     return 0;
@@ -364,62 +393,64 @@ portaudio_stop (void) {
 
 static int
 portaudio_pause (void) {
-    trace ("> portaudio_pause\n");
-    dispatch_async (command_queue, ^{
-        trace ("portaudio_pause\n");
-        dispatch_semaphore_wait(command_semaphore, DISPATCH_TIME_FOREVER);
-        if (state == DDB_PLAYBACK_STATE_STOPPED) {
-            // If option 'Resume previous session on startup' is enabled deadbeef will call:
-            // portaudio_stop() and portaudio_pause() (no portaudio_play()).
-            // If it's the case we should just set the state and do the main job later.
-            state = DDB_PLAYBACK_STATE_PAUSED;
-        }
-        PaError err;
-        err = Pa_AbortStream (stream);
-        if (err != paNoError) {
-            trace ("Failed to pause stream. %s\n", Pa_GetErrorText(err));
-        }
-        // set pause state
+    trace ("portaudio_pause\n");
+    if (state == DDB_PLAYBACK_STATE_STOPPED) {
+        // If option 'Resume previous session on startup' is enabled deadbeef will call:
+        // portaudio_stop() and portaudio_pause() (no portaudio_play()).
+        // If it's the case we should just set the state and do the main job later.
         state = DDB_PLAYBACK_STATE_PAUSED;
-        dispatch_semaphore_signal(command_semaphore);
-    });
+        return 0;
+    }
+    PaError err;
+    err = Pa_AbortStream (stream);
+    if (err != paNoError) {
+        trace ("Failed to pause stream. %s\n", Pa_GetErrorText(err));
+        return -1;
+    }
+    // set pause state
+    state = DDB_PLAYBACK_STATE_PAUSED;
     return 0;
 }
 
 static int
 portaudio_unpause (void) {
-    trace ("> portaudio_unpause\n");
+    trace ("portaudio_unpause\n");
     if (!(state == DDB_PLAYBACK_STATE_PAUSED)) {
         return -1;
     }
     return portaudio_play ();
 }
 
-// enforce config change when settings differ than on current stream
+/*
+static int portaudio_get_endiannerequested_fmt (void) {
+#if WORDS_BIGENDIAN
+    return 1;
+#else
+    return 0;
+#endif
+}
+*/
+
 static int
 portaudio_configchanged (void) {
-    dispatch_async(stream_queue, ^{
-        int portaudio_soundcard = 0;
-        {
-            deadbeef->conf_lock ();
-            const char * portaudio_soundcard_string = deadbeef->conf_get_str_fast ("portaudio_soundcard", "default");
-            if (strcmp(portaudio_soundcard_string, "default") == 0) {
-                portaudio_soundcard = Pa_GetDefaultOutputDevice ();
-                deadbeef->conf_unlock ();
-            }
-            else {
-                deadbeef->conf_unlock ();
-                portaudio_soundcard = deadbeef->conf_get_int ("portaudio_soundcard", -1);
-            }
+    int portaudio_soundcard = 0;
+    {
+        deadbeef->conf_lock ();
+        const char * portaudio_soundcard_string = deadbeef->conf_get_str_fast ("portaudio_soundcard", "default");
+        if (strcmp(portaudio_soundcard_string, "default") == 0) {
+            portaudio_soundcard = Pa_GetDefaultOutputDevice ();
+            deadbeef->conf_unlock ();
         }
-        int buffer_size_new = deadbeef->conf_get_int ("portaudio.buffer", DEFAULT_BUFFER_SIZE);
-        if ((stream && portaudio_soundcard != stream_parameters.device) || (stream_buffersize != buffer_size_new)) {
-            trace ("portaudio_configchanged: config option changed, restarting\n");
-            stream_buffersize = buffer_size_new;
-            stream_force_reset = 1;
-            deadbeef->sendmessage (DB_EV_REINIT_SOUND, 0, 0, 0);
+        else {
+            deadbeef->conf_unlock ();
+            portaudio_soundcard = deadbeef->conf_get_int ("portaudio_soundcard", -1);
         }
-    });
+    }
+    int buffer = deadbeef->conf_get_int ("portaudio.buffer", DEFAULT_BUFFER_SIZE);
+    if ((stream && Pa_IsStreamActive (stream) && portaudio_soundcard != stream_parameters.device) || (userData && userData->buffer_size != buffer)) {
+        trace ("portaudio: config option changed, restarting\n");
+        deadbeef->sendmessage (DB_EV_REINIT_SOUND, 0, 0, 0);
+    }
     return 0;
 }
 
@@ -496,28 +527,47 @@ static void portaudio_enum_soundcards (void (*callback)(const char *name, const
 
 static int
 portaudio_callback (const void *in, void *out, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *uData ) {
-    // trace ("portaudio_callback\n");
     if (!deadbeef->streamer_ok_to_read (-1)) {
-        trace ("portaudio_callback: stream not ok, wait\n");
+        trace ("portaudio_callback [%d]: wait\n",P_UDATA(uData).num);
         usleep (10000);
     }
+    if (P_UDATA(uData).i == LOOP_CLOSE) {
+        portaudio_tid = deadbeef->thread_start (portaudio_thread, NULL);
+        P_UDATA(uData).i += 1;
+    }
+    else if (P_UDATA(uData).i < LOOP_CLOSE) {
+        P_UDATA(uData).i += 1;
+    }
     if (state != DDB_PLAYBACK_STATE_PLAYING) {
-        // we shouldn't be running
-        warn ("portaudio_callback: abort!\n");
+        trace ("portaudio_callback [%d]: abort\n", P_UDATA(uData).num);
         return paAbort;
     }
-    if (framesPerBuffer != stream_lastframecount) {
-        stream_lastframecount = framesPerBuffer;
-        stream_totalsize = framesPerBuffer * stream_framesize;
+    // do not get data from streamer
+    if (P_UDATA(uData).abort == STREAM_COMPLETE) {
+        trace ("portaudio_callback [%d]: slowly aborting stream\n",P_UDATA(uData).num);
+        statusFlags = paOutputUnderflow;
+        memset (out, 0, framesPerBuffer * P_UDATA(uData).framesize);
+        return paComplete;
     }
-    int str_read_ret = deadbeef->streamer_read (out, stream_totalsize);
+    int str_read_ret = deadbeef->streamer_read (out, framesPerBuffer * P_UDATA(uData).framesize);
 
     // check for underflow
-    if (str_read_ret < stream_totalsize) {
+    if (str_read_ret < framesPerBuffer * P_UDATA(uData).framesize) {
         //statusFlags = paOutputUnderflow;
         // trim data that was not written
-        trace ("portaudio_callback: got %d frames instead of %d\n", str_read_ret/framesPerBuffer, stream_framesize);
-        memset (out+str_read_ret, 0, stream_totalsize - str_read_ret);
+        trace ("portaudio_callback [%d]: got %d frames instead of %d\n",P_UDATA(uData).num, str_read_ret/framesPerBuffer, P_UDATA(uData).framesize);
+        memset (out+str_read_ret, 0, framesPerBuffer * P_UDATA(uData).framesize - str_read_ret);
+    }
+    // check if we are supposed to play this audio data
+    if (P_UDATA(uData).abort == STREAM_COMPLETE) {
+        trace ("portaudio_callback [%d]: slowly aborting stream\n",P_UDATA(uData).num);
+        statusFlags = paOutputUnderflow;
+        memset (out, 0, framesPerBuffer * P_UDATA(uData).framesize);
+        return paComplete;
+    }
+    else if ( P_UDATA(uData).abort == STREAM_ABORT) {
+        trace ("portaudio_callback [%d]: aborting stream\n", P_UDATA(uData).num);
+        return paAbort;
     }
     return paContinue;
 }
@@ -555,12 +605,7 @@ static const char settings_dlg[] =
 
 static int
 p_portaudio_start (void) {
-    stream_queue = dispatch_queue_create("PortaudioQueue", NULL);
-    command_queue = dispatch_queue_create("PortaudioStreamQueue", NULL);
-    stream_semaphore = dispatch_semaphore_create(1);
-    command_semaphore = dispatch_semaphore_create(1);
-    dispatch_semaphore_wait(command_semaphore, DISPATCH_TIME_NOW);
-
+    mutex = deadbeef->mutex_create ();
     PaError err;
     err = Pa_Initialize ();
     if (err != paNoError) {
@@ -573,6 +618,7 @@ p_portaudio_start (void) {
 
 static int
 p_portaudio_stop (void) {
+    deadbeef->mutex_free (mutex);
     PaError err;
     err = Pa_Terminate ();
     if (err != paNoError) {
@@ -592,16 +638,12 @@ static DB_output_t plugin = {
     .plugin.api_vmajor = 1,
     .plugin.api_vminor = 10,
     .plugin.version_major = 1,
-    .plugin.version_minor = 8,
+    .plugin.version_minor = 6,
     .plugin.type = DB_PLUGIN_OUTPUT,
     .plugin.id = "portaudio",
     .plugin.name = "PortAudio output plugin",
     .plugin.descr = "This plugin plays audio using PortAudio library.\n"
     "\n"
-    "Changes in version 1.8:\n"
-    "    * Fix for 8 bit depth audio.\n"
-    "Changes in version 1.7:\n"
-    "    * Converted plugin to use libdispatch.\n"
     "Changes in version 1.6:\n"
     "    * Fixed ending of stream when option 'Stop after current track/album' is used.\n"
     "Changes in version 1.5:\n"
@@ -617,7 +659,7 @@ static DB_output_t plugin = {
     "    * Better format handling, less possibility of playing static",
     .plugin.copyright =
     "PortAudio output plugin for DeaDBeeF Player\n"
-    "Copyright (C) 2017-2023 Jakub Wasylków\n"
+    "Copyright (C) 2017 Jakub Wasylków\n"
     "\n"
     "This software is provided 'as-is', without any express or implied\n"
     "warranty.  In no event will the authors be held liable for any damages\n"
-- 
2.49.0

