Files
racket-sound-lib/ao-play-async/ao_playasync.c
2026-04-16 08:29:32 +02:00

581 lines
14 KiB
C

#include "ao_playasync.h"
#ifdef WIN32
#include <windows.h>
#define USE_WINDOWS_THREADS
#define sleep_ms(ms) Sleep(ms)
#else
#define USE_PTHREADS
#define sleep_ms(ms) usleep(ms * 1000)
#endif
#ifdef USE_WINDOWS_THREADS
#define MUTEX_LOCK(m) WaitForSingleObject(m, INFINITE)
#define MUTEX_UNLOCK(m) ReleaseMutex(m)
#define SEM_WAIT(sem, ms) (WaitForSingleObject(sem, ms) == WAIT_OBJECT_0)
#define SEM_TRYWAIT(sem) (WaitForSingleObject(sem, 0) == WAIT_OBJECT_0)
#define SEM_POST(sem) ReleaseSemaphore(sem, 1, NULL)
#define YIELD() sleep_ms(5)
#endif
#ifdef USE_PTHREADS
#include <pthread.h>
#include <semaphore.h>
#include <sched.h>
#define TIME_NS_IN_MSEC 1000000ULL
static void makeSemTimeoutTime(struct timespec *ts, int ms) {
clock_gettime(CLOCK_REALTIME, ts);
ts->tv_sec += ms / 1000;
ts->tv_nsec += (ms % 1000) * TIME_NS_IN_MSEC;
if (ts->tv_nsec >= 1000000000L) {
ts->tv_sec++;
ts->tv_nsec = ts->tv_nsec - 1000000000L;
}
}
static int _SEM_WAIT(sem_t *sem, int ms)
{
struct timespec ts;
makeSemTimeoutTime(&ts, ms);
int r = sem_timedwait(sem, &ts);
return (r == 0);
}
static int msleep(long msec)
{
struct timespec ts;
int res;
if (msec < 0) {
return -1;
}
ts.tv_sec = msec / 1000;
ts.tv_nsec = (msec % 1000) * 1000000;
res = nanosleep(&ts, &ts);
return res;
}
static void yield()
{
msleep(5);
}
#define MUTEX_LOCK(m) pthread_mutex_lock(&m)
#define MUTEX_UNLOCK(m) pthread_mutex_unlock(&m)
#define SEM_WAIT(sem, ms) _SEM_WAIT(&sem, ms)
#define SEM_TRYWAIT(sem) (sem_trywait(&sem) == 0)
#define SEM_POST(sem) sem_post(&sem)
#define YIELD() yield()
#endif
#ifndef WIN32
#include <unistd.h>
#endif
#include <malloc.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <ao/ao.h>
typedef enum {
PLAY = 1,
STOP = 2
} Command_t;
typedef struct _queue_ {
Command_t command;
void *buf;
int buflen;
double at_second;
double music_duration;
int music_id;
struct _queue_ *next;
struct _queue_ *prev;
} Queue_t;
typedef int(*ao_play_func_t)(void *, char *, uint32_t);
typedef struct {
Queue_t *play_queue;
Queue_t *last_frame;
int paused;
ao_device *ao_device;
#ifdef USE_WINDOWS_THREADS
HANDLE mutex;
HANDLE pause_mutex;
HANDLE clear_mutex;
HANDLE thread;
DWORD thread_id;
HANDLE queue_sem;
#endif
#ifdef USE_PTHREADS
pthread_mutex_t mutex;
pthread_mutex_t pause_mutex;
pthread_mutex_t clear_mutex;
pthread_t thread;
sem_t queue_sem;
#endif
double at_second;
double music_duration;
int at_music_id;
int buf_size;
} AO_Handle;
static Queue_t *get(AO_Handle *h, int ms_wait)
{
Queue_t *q = NULL;
int r;
if (ms_wait <= 0) {
r = SEM_TRYWAIT(h->queue_sem);
} else {
r = SEM_WAIT(h->queue_sem, ms_wait);
}
if (r) {
MUTEX_LOCK(h->mutex);
if (h->play_queue != NULL) { // Clear could have cleared the play_queue.
q = h->play_queue;
h->play_queue = h->play_queue->next;
if (h->play_queue == NULL) {
h->last_frame = NULL;
} else {
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;
elem->prev = NULL;
h->last_frame = h->play_queue;
} else {
h->last_frame->next = elem;
elem->prev = h->last_frame;
elem->next = NULL;
h->last_frame = elem;
}
h->buf_size += elem->buflen;
SEM_POST(h->queue_sem);
MUTEX_UNLOCK(h->mutex);
}
static Queue_t *new_elem(int command, int music_id, double at_second, double music_duration, int buf_len, void *buf)
{
Queue_t *q = (Queue_t *) malloc(sizeof(Queue_t));
void *new_buf;
if (buf_len != 0) {
new_buf = (void *) malloc(buf_len);
memcpy(new_buf, buf, buf_len);
} else {
new_buf = NULL;
}
q->music_id = music_id;
q->at_second = at_second;
q->music_duration = music_duration;
q->buf = new_buf;
q->buflen = buf_len;
q->command = (Command_t) command;
q->next = NULL;
q->prev = NULL;
return q;
}
static void del_elem(Queue_t *q)
{
if (q->buflen != 0) {
free(q->buf);
}
free(q);
}
static void clear(AO_Handle *h)
{
//fprintf(stderr, "Wait for clear mutex\n");
MUTEX_LOCK(h->clear_mutex);
//fprintf(stderr, "Starting clear\n");
int count = 0;
Queue_t *q = get(h, 0);
while (q != NULL) {
del_elem(q);
count += 1;
q = get(h, 0);
}
fprintf(stderr, "%d elements cleared\n", count);
MUTEX_UNLOCK(h->clear_mutex);
}
#ifdef USE_PTHREADS
static void *run(void *arg)
#endif
#ifdef USE_WINDOWS_THREADS
static DWORD run(LPVOID arg)
#endif
{
AO_Handle *handle = (AO_Handle *) arg;
int go_on = (1 == 1);
while(go_on) {
MUTEX_LOCK(handle->pause_mutex);
MUTEX_UNLOCK(handle->pause_mutex);
MUTEX_LOCK(handle->clear_mutex);
Queue_t *q = get(handle, 250);
MUTEX_UNLOCK(handle->clear_mutex);
if (q != NULL) {
handle->at_second = q->at_second;
handle->music_duration = q->music_duration;
handle->at_music_id = q->music_id;
if (q->command == STOP) {
go_on = (1 == 0);
} else {
ao_play(handle->ao_device, (char *) q->buf, q->buflen);
}
del_elem(q);
} else {
YIELD();
}
}
#ifdef USE_PTHREADS
return NULL;
#endif
#ifdef USE_WINDOWS_THREADS
return 0;
#endif
}
int ao_async_version()
{
return VERSION;
}
#ifdef _WIN32
#else
#include <dlfcn.h>
#endif
#ifdef _WIN32
static void at_exit_shutdown_ao(void)
#else
static void at_exit_shutdown_ao()
#endif
{
ao_shutdown();
}
static void init_ao()
{
static int init = 0;
if (!init) {
ao_initialize();
atexit(at_exit_shutdown_ao);
}
}
void *ao_create_async(int bits, int rate, int channels, int byte_format)
{
init_ao();
AO_Handle *handle = (AO_Handle *) malloc(sizeof(AO_Handle));
ao_sample_format fmt;
fmt.bits = bits;
fmt.rate = rate;
fmt.byte_format = byte_format;
fmt.channels = channels;
fmt.matrix = NULL;
ao_device *dev = ao_open_live(ao_default_driver_id(), &fmt, NULL);
if (dev == NULL) {
fprintf(stderr, "Cannot open ao-device, error code = %d\n", errno);
return NULL;
}
handle->ao_device = dev;
handle->play_queue = NULL;
handle->last_frame = NULL;
handle->at_second = -1;
handle->at_music_id = -1;
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 c = PTHREAD_MUTEX_INITIALIZER;
handle->clear_mutex = c;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
handle->mutex = m;
sem_init(&handle->queue_sem, 0, 0);
pthread_create(&handle->thread, NULL, run, handle);
#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->clear_mutex = CreateMutex(NULL,
FALSE,
NULL);
handle->queue_sem = CreateSemaphore(NULL, 0, 1000000, NULL);
handle->thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) run, handle, 0, &handle->thread_id);
#endif
MUTEX_UNLOCK(handle->pause_mutex);
MUTEX_UNLOCK(handle->clear_mutex);
return (void *) handle;
}
void ao_stop_async(void *ao_handle)
{
AO_Handle *h = (AO_Handle *) ao_handle;
fprintf(stderr, "stopping ao_async, calling clear\n");
clear(h);
fprintf(stderr, "queue cleared\n");
if (h->paused) {
MUTEX_UNLOCK(h->pause_mutex);
}
Queue_t *q = new_elem(STOP, 0, 0.0, 0.0, 0, NULL);
add(h, q);
fprintf(stderr, "stop command queued\n");
#ifdef USE_PTHREADS
void *retval;
pthread_join(h->thread, &retval);
#endif
#ifdef USE_WINDOWS_THREADS
WaitForSingleObject(h->thread, INFINITE);
CloseHandle(h->thread);
CloseHandle(h->mutex);
#endif
ao_close(h->ao_device);
fprintf(stderr, "device closed\n");
free(h);
fprintf(stderr, "async handle freed\n");
}
/*
(define (abs x) (if (>= x 0) x (* x -1)))
(define (make-sample-bytes sample bytes-per-sample endianess)
(letrec ((mk (lambda (i d)
(if (< i bytes-per-sample)
(cons (bitwise-and d 255)
(mk (+ i 1) (arithmetic-shift d -8)))
'()))))
(let ((bytes (mk 0 sample)))
(if (eq? endianess 'big-endian)
(reverse bytes)
bytes))))
;(get-sample (lambda (k channel)
; (let ((chan-buf (list-ref buffer channel)))
; (vector-ref chan-buf k))))
)
;(letrec ((i 0)
; (fill (lambda (k channel)
; (if (< k buf-len)
; (if (< channel channels)
; (let* ((sample (get-sample k channel))
; (bytes (make-sample-bytes sample bytes-per-sample endianess))
; )
; (for-each (lambda (byte)
; (ptr-set! audio _byte i byte)
; (set! i (+ i 1)))
; bytes)
; ;; process sample
; (fill k (+ channel 1)))
; (fill (+ k 1) 0))
; 'filled))
; ))
; (fill 0 0)
*/
#define AO_FMT_LITTLE 1
#define AO_FMT_BIG 2
#define AO_FMT_NATIVE 4
static inline void make_sample_bytes(int32_t sample, int bytes_per_sample, int endianess, unsigned char b[4])
{
int i;
for (i = 0; i < bytes_per_sample; i++) {
b[i] = sample&0xff;
sample = sample >> 8;
}
if (endianess == AO_FMT_BIG) {
unsigned char b1[4] = { 0, 0, 0, 0 };
for(i = 0; i < bytes_per_sample; i++) {
b1[bytes_per_sample - i - 1] = b[i];
}
for(i = 0; i < bytes_per_sample; i++) {
b[i] = b1[i];
}
}
}
void *convertFlac(void *mem, int buf_len, BufferInfo_t *info, int *audio_size)
{
// buf_size equals number of samples of 32bit for all channels. So buf_size for flac = 4 * buf_len * channels
int bytes = info->sample_bits / 8;
int endianness = info->endiannes;
int store_size = info->channels * bytes * buf_len;
unsigned char *new_mem = (unsigned char *) malloc(store_size);
*audio_size = store_size;
int32_t **buffer = (int32_t **) mem;
int i, k, channel;
i = 0;
for(k = 0; k < buf_len; k++) {
for(channel = 0; channel < info->channels; channel++) {
int32_t *chan = buffer[channel];
int32_t sample = chan[k];
unsigned char b[4];
make_sample_bytes(sample, bytes, endianness, b);
for(int j = 0; j < bytes; j++) {
new_mem[i++] = b[j];
}
}
}
return (void *) new_mem;
}
void ao_play_async(void *ao_handle, int music_id, double at_second, double music_duration, int buf_size, void *mem, BufferInfo_t info)
{
AO_Handle *h = (AO_Handle *) ao_handle;
Queue_t *q = NULL;
switch(info.type) {
case flac: {
int store_size = 0;
void *store_mem = convertFlac(mem, buf_size, &info, &store_size);
q = new_elem(PLAY, music_id, at_second, music_duration, store_size, store_mem);
free(store_mem);
}
break;
case ao: {
q = new_elem(PLAY, music_id, at_second, music_duration, buf_size, mem);
}
break;
case mpg123: {
static int warned = 0;
if (!warned) {
warned = 1;
fprintf(stderr, "format mpg123 not supported yet\n");
}
return;
}
break;
case ao_ogg: {
static int warned = 0;
if (!warned) {
warned = 1;
fprintf(stderr, "format ao_ogg not supported yet\n");
}
return;
}
break;
}
add(h, q);
}
void ao_clear_async(void *ao_handle)
{
AO_Handle *h = (AO_Handle *) ao_handle;
clear(h);
}
double ao_is_at_second_async(void *ao_handle)
{
AO_Handle *h = (AO_Handle *) ao_handle;
MUTEX_LOCK(h->mutex);
double s = h->at_second;
MUTEX_UNLOCK(h->mutex);
return s;
}
int ao_is_at_music_id_async(void *ao_handle)
{
AO_Handle *h = (AO_Handle *) ao_handle;
MUTEX_LOCK(h->mutex);
int s = h->at_music_id;
MUTEX_UNLOCK(h->mutex);
return s;
}
double ao_music_duration_async(void *ao_handle)
{
AO_Handle *h = (AO_Handle *) ao_handle;
MUTEX_LOCK(h->mutex);
double duration = h->music_duration;
MUTEX_UNLOCK(h->mutex);
return duration;
}
int ao_bufsize_async(void *ao_handle)
{
AO_Handle *h = (AO_Handle *) ao_handle;
MUTEX_LOCK(h->mutex);
int s = h->buf_size;
MUTEX_UNLOCK(h->mutex);
return s;
}
void ao_pause_async(void *ao_handle, int paused)
{
AO_Handle *h = (AO_Handle *) ao_handle;
MUTEX_LOCK(h->mutex);
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);
}