From d33bdb34825e01487ee31a8e3c6ebefc26db9e05 Mon Sep 17 00:00:00 2001 From: Hans Dijkema Date: Tue, 14 Apr 2026 15:08:32 +0200 Subject: [PATCH] created semaphore for queue and mutex for pause --- Makefile | 3 ++ ao-play-async/ao_playasync.c | 75 +++++++++++++++++++++++------------ lib/linux-x86_64.zip | Bin 665556 -> 665683 bytes 3 files changed, 52 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index 9505770..90083c1 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,9 @@ install: all SUBDIR=`racket -e "(display (format \"~a-~a\" (system-type 'os*) (system-type 'arch)))"`; \ FILES=`ls build/*.dll` 2>/dev/null; if [ "$$FILES" != "" ]; then cp $$FILES lib/$$SUBDIR; fi +test: install + cp lib/linux-x86_64/*.so ~/.local/share/racket/racket-sound-lib/linux-x86_64 + zip: install SUBDIR=`racket -e "(display (format \"~a-~a\" (system-type 'os*) (system-type 'arch)))"`; \ (cd lib; zip -y -r -9 $$SUBDIR.zip $$SUBDIR) diff --git a/ao-play-async/ao_playasync.c b/ao-play-async/ao_playasync.c index fdb4e7d..dff68b5 100644 --- a/ao-play-async/ao_playasync.c +++ b/ao-play-async/ao_playasync.c @@ -16,6 +16,7 @@ #ifdef USE_PTHREADS #include + #include #define MUTEX_LOCK(m) pthread_mutex_lock(&m) #define MUTEX_UNLOCK(m) pthread_mutex_unlock(&m) #endif @@ -55,21 +56,26 @@ typedef int(*ao_play_func_t)(void *, char *, uint32_t); typedef struct { Queue_t *play_queue; Queue_t *last_frame; + sem_t queue_sem; + + int paused; + ao_device *ao_device; #ifdef USE_WINDOWS_THREADS HANDLE mutex; + HANDLE pause_mutex; HANDLE thread; DWORD thread_id; #endif #ifdef USE_PTHREADS pthread_mutex_t mutex; + pthread_mutex_t pause_mutex; pthread_t thread; #endif double at_second; double music_duration; ao_play_func_t ao_play_f; int buf_size; - int paused; } AO_Handle; //static int(*ao_play)(void *device, char *samples, uint32_t n) = NULL; @@ -83,6 +89,8 @@ static Queue_t *front(AO_Handle *h) static Queue_t *get(AO_Handle *h) { + sem_wait(&h->queue_sem); + MUTEX_LOCK(h->mutex); assert(h->play_queue != NULL); Queue_t *q = h->play_queue; h->play_queue = h->play_queue->next; @@ -92,11 +100,13 @@ static Queue_t *get(AO_Handle *h) h->play_queue->prev = NULL; } h->buf_size -= q->buflen; + MUTEX_UNLOCK(h->mutex); return q; } static void add(AO_Handle *h, Queue_t *elem) { + MUTEX_LOCK(h->mutex); if (h->last_frame == NULL) { h->play_queue = elem; elem->next = NULL; @@ -109,6 +119,8 @@ static void add(AO_Handle *h, Queue_t *elem) h->last_frame = elem; } h->buf_size += elem->buflen; + MUTEX_UNLOCK(h->mutex); + sem_post(&h->queue_sem); } static Queue_t *new_elem(int command, double at_second, double music_duration, int buf_len, void *buf) @@ -126,7 +138,7 @@ static Queue_t *new_elem(int command, double at_second, double music_duration, i q->music_duration = music_duration; q->buf = new_buf; q->buflen = buf_len; - q->command = command; + q->command = (Command_t) command; q->next = NULL; q->prev = NULL; return q; @@ -160,27 +172,22 @@ static DWORD run(LPVOID arg) int go_on = (1 == 1); while(go_on) { - MUTEX_LOCK(handle->mutex); - int has_frames = (!handle->paused) && (handle->play_queue != NULL); + MUTEX_LOCK(handle->pause_mutex); + MUTEX_UNLOCK(handle->pause_mutex); - if (has_frames) { - Queue_t *q = get(handle); - handle->at_second = q->at_second; - handle->music_duration = q->music_duration; - MUTEX_UNLOCK(handle->mutex); + Queue_t *q = get(handle); - if (q->command == STOP) { - go_on = (1 == 0); - } else { - //fprintf(stderr, "playing buf at %lf\n", handle->at_second); - handle->ao_play_f(handle->ao_device, q->buf, q->buflen); - } + handle->at_second = q->at_second; + handle->music_duration = q->music_duration; - del_elem(q); + if (q->command == STOP) { + go_on = (1 == 0); } else { - MUTEX_UNLOCK(handle->mutex); - sleep_ms(5); // sleep for 5ms + //fprintf(stderr, "playing buf at %lf\n", handle->at_second); + handle->ao_play_f(handle->ao_device, (char *) q->buf, q->buflen); } + + del_elem(q); } #ifdef USE_PTHREADS @@ -207,23 +214,33 @@ void *ao_create_async(void *ao_device_yeah, void *ao_play_f) handle->last_frame = NULL; handle->at_second = -1; - handle->ao_play_f = ao_play_f; + handle->ao_play_f = (ao_play_func_t) ao_play_f; handle->buf_size = 0; + handle->paused = (1 == 0); #ifdef USE_PTHREADS + pthread_mutex_t p = PTHREAD_MUTEX_INITIALIZER; + handle->pause_mutex = p; pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; handle->mutex = m; pthread_create(&handle->thread, NULL, run, handle); + sem_init(&handle->queue_sem, 0, 0); #endif #ifdef USE_WINDOWS_THREADS handle->mutex = CreateMutex(NULL, // default security attributes FALSE, // initially not owned NULL); + handle->pause_mutex = CreateMutex(NULL, // default security attributes + FALSE, // initially not owned + NULL); handle->thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) run, handle, 0, &handle->thread_id); + // TODO Windows Semaphores! #endif + MUTEX_UNLOCK(handle->pause_mutex); + return (void *) handle; } @@ -231,11 +248,9 @@ void ao_stop_async(void *ao_handle) { AO_Handle *h = (AO_Handle *) ao_handle; - MUTEX_LOCK(h->mutex); clear(h); Queue_t *q = new_elem(STOP, 0.0, 0.0, 0, NULL); add(h, q); - MUTEX_UNLOCK(h->mutex); #ifdef USE_PTHREADS void *retval; @@ -375,17 +390,13 @@ void ao_play_async(void *ao_handle, double at_second, double music_duration, int break; } - MUTEX_LOCK(h->mutex); add(h, q); - MUTEX_UNLOCK(h->mutex); } void ao_clear_async(void *ao_handle) { AO_Handle *h = (AO_Handle *) ao_handle; - MUTEX_LOCK(h->mutex); clear(h); - MUTEX_UNLOCK(h->mutex); } double ao_is_at_second_async(void *ao_handle) @@ -419,7 +430,19 @@ void ao_pause_async(void *ao_handle, int paused) { AO_Handle *h = (AO_Handle *) ao_handle; MUTEX_LOCK(h->mutex); - h->paused = paused; + + if (h->paused) { + if (!paused) { + h->paused = paused; + MUTEX_UNLOCK(h->pause_mutex); + } + } else { + if (paused) { + h->paused = paused; + MUTEX_LOCK(h->pause_mutex); + } + } + MUTEX_UNLOCK(h->mutex); } diff --git a/lib/linux-x86_64.zip b/lib/linux-x86_64.zip index 01e7efd75b2ec56e5041cd440e4ec5e28c6dc6b2..819d04103e547b4a55ba8ba6a87123240fe958b4 100644 GIT binary patch delta 4947 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 RVicF5CkibFMkNXW006NS)zbg~ delta 4819 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