899 lines
22 KiB
C
899 lines
22 KiB
C
#include "ao_playasync.h"
|
|
#include "../ffi_version.h"
|
|
|
|
#define TRUE (1==1)
|
|
#define FALSE (1==0)
|
|
|
|
#if defined(_WIN32) || defined(WIN32)
|
|
#define AO_ASYNC_WINDOWS
|
|
#include <windows.h>
|
|
#define USE_WINDOWS_THREADS
|
|
#define sleep_ms(ms) Sleep(ms)
|
|
#else
|
|
#ifdef __APPLE__
|
|
#define AO_ASYNC_APPLE
|
|
#define USE_DISPATCH
|
|
#include <time.h>
|
|
#define sleep_ms(ms) msleep(ms)
|
|
#else
|
|
#define USE_PTHREADS
|
|
#include <time.h>
|
|
#include <sched.h>
|
|
#define sleep_ms(ms) usleep(ms * 1000)
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(USE_PTHREADS) || defined(USE_DISPATCH)
|
|
|
|
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);
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_DISPATCH
|
|
#include <pthread.h>
|
|
#include <dispatch/dispatch.h>
|
|
#include <stdint.h>
|
|
|
|
static inline dispatch_time_t makeDispatchTimeoutTime(int ms)
|
|
{
|
|
return dispatch_time(DISPATCH_TIME_NOW,
|
|
(int64_t)ms * NSEC_PER_MSEC);
|
|
}
|
|
|
|
static int _SEM_WAIT(dispatch_semaphore_t sem, int ms)
|
|
{
|
|
return dispatch_semaphore_wait(sem, makeDispatchTimeoutTime(ms)) == 0;
|
|
}
|
|
|
|
#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) (dispatch_semaphore_wait(sem, DISPATCH_TIME_NOW) == 0)
|
|
#define SEM_WAIT_INFINITE(sem) (dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER) == 0)
|
|
#define SEM_POST(sem) dispatch_semaphore_signal(sem)
|
|
#define YIELD() sleep_ms(5)
|
|
#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_WAIT_INFINITE(sem) (WaitForSingleObject(sem, INFINITE) == 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>
|
|
|
|
#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);
|
|
}
|
|
|
|
#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_WAIT_INFINITE(sem) (sem_wait(&sem) == 0)
|
|
#define SEM_TRYWAIT(sem) (sem_trywait(&sem) == 0)
|
|
#define SEM_POST(sem) sem_post(&sem)
|
|
#define YIELD() yield()
|
|
#endif
|
|
|
|
#ifndef AO_ASYNC_WINDOWS
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifndef AO_ASYNC_APPLE
|
|
#include <malloc.h>
|
|
#endif
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <ao/ao.h>
|
|
#include <errno.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;
|
|
|
|
struct AO_Handle;
|
|
|
|
typedef struct {
|
|
Queue_t *play_queue;
|
|
Queue_t *last_frame;
|
|
|
|
int paused;
|
|
int stopped;
|
|
|
|
ao_device *ao_device;
|
|
int requested_bits_per_sample;
|
|
int dev_bits_per_sample;
|
|
int dev_endianess;
|
|
int dev_channels;
|
|
int dev_rate;
|
|
|
|
#ifdef USE_WINDOWS_THREADS
|
|
HANDLE mutex;
|
|
HANDLE clear_mutex;
|
|
HANDLE thread;
|
|
DWORD thread_id;
|
|
HANDLE queue_sem;
|
|
HANDLE pause_sem;
|
|
#endif
|
|
#if defined(USE_PTHREADS) || defined(USE_DISPATCH)
|
|
pthread_mutex_t mutex;
|
|
pthread_mutex_t clear_mutex;
|
|
pthread_t thread;
|
|
#ifdef USE_PTHREADS
|
|
sem_t queue_sem;
|
|
sem_t pause_sem;
|
|
#endif
|
|
#ifdef USE_DISPATCH
|
|
dispatch_semaphore_t queue_sem;
|
|
dispatch_semaphore_t pause_sem;
|
|
#endif
|
|
#endif
|
|
double at_second;
|
|
double music_duration;
|
|
int at_music_id;
|
|
int buf_size;
|
|
int volume_in_10000; // volume in 10000 steps, i.e. 10000 equals 100%
|
|
} AO_Handle;
|
|
|
|
static inline int stopped(AO_Handle *h, int lock_mutex)
|
|
{
|
|
if (lock_mutex) MUTEX_LOCK(h->mutex);
|
|
int stopped = h->stopped;
|
|
if (lock_mutex) MUTEX_UNLOCK(h->mutex);
|
|
return stopped;
|
|
}
|
|
|
|
static void del_elem(Queue_t *q);
|
|
|
|
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, int in_clear)
|
|
{
|
|
if (!in_clear) {
|
|
MUTEX_LOCK(h->clear_mutex);
|
|
}
|
|
|
|
MUTEX_LOCK(h->mutex);
|
|
if (!in_clear && stopped(h, FALSE)) {
|
|
MUTEX_UNLOCK(h->mutex);
|
|
MUTEX_UNLOCK(h->clear_mutex);
|
|
del_elem(elem);
|
|
return;
|
|
}
|
|
|
|
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);
|
|
|
|
if (!in_clear) {
|
|
MUTEX_UNLOCK(h->clear_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));
|
|
if (q == NULL) {
|
|
fprintf(stderr, "new_elem: Cannot allocate Queue Element!\n");
|
|
return NULL;
|
|
}
|
|
|
|
void *new_buf;
|
|
|
|
if (buf_len > 0) {
|
|
new_buf = (void *) malloc(buf_len);
|
|
if (new_buf != NULL) {
|
|
memcpy(new_buf, buf, buf_len);
|
|
} else {
|
|
fprintf(stderr, "new_elem: Cannot allocate memory of size %d\n", buf_len);
|
|
buf_len = 0;
|
|
free(q);
|
|
return NULL;
|
|
}
|
|
} 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, int do_stop)
|
|
{
|
|
//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);
|
|
|
|
if (do_stop) {
|
|
Queue_t *q = new_elem(STOP, 0, 0.0, 0.0, 0, NULL);
|
|
if (q == NULL) {
|
|
fprintf(stderr, "Unexpected! Cannot allocate STOP element, aborting\n");
|
|
abort();
|
|
}
|
|
add(h, q, TRUE);
|
|
}
|
|
|
|
MUTEX_UNLOCK(h->clear_mutex);
|
|
}
|
|
|
|
static int inline littleEndian()
|
|
{
|
|
int n = 1;
|
|
return (*(char *)&n) == 1;
|
|
}
|
|
|
|
/*
|
|
static void inline adjustVolume(AO_Handle *handle, char *_buf, int buf_size, int volume_in_10000)
|
|
{
|
|
int bytes_per_sample = (handle->dev_bits_per_sample >> 3);
|
|
register int endianess = handle->dev_endianess;
|
|
register unsigned char *buf = (unsigned char *) _buf;
|
|
|
|
register int i;
|
|
register long long sample;
|
|
register int k;
|
|
|
|
int little_endian = (endianess == AO_FMT_LITTLE);
|
|
if (!little_endian && endianess == AO_FMT_NATIVE) little_endian = littleEndian();
|
|
|
|
for(i = 0; i < buf_size; i += bytes_per_sample) {
|
|
if (little_endian) {
|
|
sample = buf[bytes_per_sample + i - 1] > 127 ? -1 : 0;
|
|
for(k = bytes_per_sample + i - 1; k >= i; k--) {
|
|
sample <<= 8;
|
|
sample |= buf[k];
|
|
}
|
|
} else {
|
|
sample = (buf[i] > 127) ? -1 : 0;
|
|
for(k = i; k < (i + bytes_per_sample); k++) {
|
|
sample <<= 8;
|
|
sample += buf[k];
|
|
}
|
|
}
|
|
sample *= volume_in_10000;
|
|
sample /= 10000;
|
|
if (little_endian) {
|
|
for(k = i; k < (i + bytes_per_sample); k++) {
|
|
buf[k] = sample&0xff;
|
|
sample >>= 8;
|
|
}
|
|
} else {
|
|
for(k = i + bytes_per_sample - 1; k >= i; k--) {
|
|
buf[k] = sample&0xff;
|
|
sample >>= 8;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
|
|
static inline int32_t read_sample(unsigned char *mem, int req_bytes, int little_endian)
|
|
{
|
|
uint32_t v = 0;
|
|
|
|
for (int i = 0; i < req_bytes; i++) {
|
|
int idx = little_endian ? i : (req_bytes - i - 1);
|
|
v |= ((uint32_t) mem[idx]) << (8 * i);
|
|
}
|
|
|
|
if (req_bytes < 4) {
|
|
int bits = req_bytes * 8;
|
|
uint32_t sign = 1u << (bits - 1);
|
|
v = (v ^ sign) - sign;
|
|
}
|
|
|
|
return (int32_t) v;
|
|
}
|
|
|
|
static inline void store_sample(unsigned char *dst, int32_t in_sample, int out_bytes, int is_little_endian)
|
|
{
|
|
uint32_t sample = (uint32_t) in_sample;
|
|
for (int i = 0; i < out_bytes; i++) {
|
|
int idx = is_little_endian ? i : (out_bytes - i - 1);
|
|
dst[idx] = sample & 0xff;
|
|
sample >>= 8;
|
|
}
|
|
}
|
|
|
|
|
|
static inline void adjustVolume(AO_Handle *handle,
|
|
char *_buf,
|
|
int buf_size,
|
|
int volume_in_10000)
|
|
{
|
|
int bytes_per_sample = handle->dev_bits_per_sample >> 3;
|
|
int endianess = handle->dev_endianess;
|
|
int little_endian = (endianess == AO_FMT_LITTLE);
|
|
|
|
if (!little_endian && endianess == AO_FMT_NATIVE) {
|
|
little_endian = littleEndian();
|
|
}
|
|
|
|
unsigned char *buf = (unsigned char *) _buf;
|
|
|
|
for (int i = 0; i + bytes_per_sample <= buf_size; i += bytes_per_sample) {
|
|
int32_t sample = read_sample(buf + i, bytes_per_sample, little_endian);
|
|
int64_t scaled = ((int64_t) sample * volume_in_10000) / 10000;
|
|
store_sample(buf + i, (int32_t) scaled, bytes_per_sample, little_endian);
|
|
}
|
|
}
|
|
|
|
#if defined(USE_PTHREADS) || defined(USE_DISPATCH)
|
|
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) {
|
|
SEM_WAIT_INFINITE(handle->pause_sem);
|
|
SEM_POST(handle->pause_sem);
|
|
MUTEX_LOCK(handle->clear_mutex);
|
|
Queue_t *q = get(handle, 250);
|
|
MUTEX_UNLOCK(handle->clear_mutex);
|
|
if (q != NULL) {
|
|
|
|
MUTEX_LOCK(handle->mutex);
|
|
handle->at_second = q->at_second;
|
|
handle->music_duration = q->music_duration;
|
|
handle->at_music_id = q->music_id;
|
|
int volume = handle->volume_in_10000;
|
|
MUTEX_UNLOCK(handle->mutex);
|
|
|
|
if (volume != 10000 && q->command == PLAY) {
|
|
// adjust volume
|
|
adjustVolume(handle, q->buf, q->buflen, volume);
|
|
}
|
|
|
|
if (q->command == STOP) {
|
|
go_on = FALSE;
|
|
} else {
|
|
if (!ao_play(handle->ao_device, (char *) q->buf, q->buflen)) {
|
|
fprintf(stderr, "Unexpected, ao_play returns 0 --> ao device must be closed\n");
|
|
fprintf(stderr, "Stopping play loop\n");
|
|
go_on = FALSE;
|
|
MUTEX_LOCK(handle->mutex);
|
|
handle->stopped = TRUE;
|
|
MUTEX_UNLOCK(handle->mutex);
|
|
}
|
|
}
|
|
|
|
del_elem(q);
|
|
} else {
|
|
YIELD();
|
|
}
|
|
}
|
|
|
|
#if defined(USE_PTHREADS) || defined(USE_DISPATCH)
|
|
return NULL;
|
|
#endif
|
|
#ifdef USE_WINDOWS_THREADS
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int ao_async_version()
|
|
{
|
|
return ffi_version();
|
|
}
|
|
|
|
#ifdef AO_ASYNC_WINDOWS
|
|
#else
|
|
#include <dlfcn.h>
|
|
#endif
|
|
|
|
#ifdef AO_ASYNC_WINDOWS
|
|
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);
|
|
init = 1;
|
|
}
|
|
}
|
|
|
|
|
|
static ao_device *try_open_device(int bits, int rate, int channels, int byte_format,
|
|
const char *wav_file_output,
|
|
int *opened_bits)
|
|
{
|
|
int candidates[3];
|
|
int n = 0;
|
|
|
|
candidates[n++] = bits;
|
|
|
|
if (bits > 24) candidates[n++] = 24;
|
|
if (bits > 16) candidates[n++] = 16;
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
ao_sample_format fmt;
|
|
fmt.bits = candidates[i];
|
|
fmt.rate = rate;
|
|
fmt.byte_format = byte_format;
|
|
fmt.channels = channels;
|
|
fmt.matrix = NULL;
|
|
|
|
ao_device *dev;
|
|
|
|
if (wav_file_output != NULL) {
|
|
int driver_id = ao_driver_id("wav");
|
|
dev = ao_open_file(driver_id, wav_file_output, 1, &fmt, NULL);
|
|
} else {
|
|
dev = ao_open_live(ao_default_driver_id(), &fmt, NULL);
|
|
}
|
|
|
|
if (dev != NULL) {
|
|
*opened_bits = candidates[i];
|
|
return dev;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
static inline int32_t convert_bits(int32_t sample, int req_bits, int out_bits)
|
|
{
|
|
if (req_bits > out_bits) {
|
|
sample >>= (req_bits - out_bits);
|
|
} else if (req_bits < out_bits) {
|
|
sample <<= (out_bits - req_bits);
|
|
}
|
|
return sample;
|
|
}
|
|
*/
|
|
|
|
static inline int32_t convert_bits(int32_t sample, int req_bits, int out_bits)
|
|
{
|
|
if (req_bits == out_bits) return sample;
|
|
|
|
int shift = req_bits > out_bits
|
|
? req_bits - out_bits
|
|
: out_bits - req_bits;
|
|
|
|
if (req_bits > out_bits) {
|
|
return sample / (1 << shift);
|
|
} else {
|
|
return (int32_t)((int64_t)sample * ((int64_t)1 << shift));
|
|
}
|
|
}
|
|
|
|
|
|
static void *convert_req_to_real(AO_Handle *h, void *mem, int mem_size, BufferInfo_t *info, int *out_size)
|
|
{
|
|
if (mem_size <= 0) {
|
|
*out_size = 0;
|
|
return NULL;
|
|
}
|
|
|
|
int endianess = h->dev_endianess;
|
|
int little_endian = (endianess == AO_FMT_LITTLE);
|
|
if (!little_endian && endianess == AO_FMT_NATIVE) little_endian = littleEndian();
|
|
|
|
int requested_bits = info->sample_bits; // h->requested_bits_per_sample;
|
|
int output_bits = h->dev_bits_per_sample;
|
|
|
|
int req_bytes = (requested_bits / 8);
|
|
int out_bytes = (output_bits / 8);
|
|
|
|
int samples = (mem_size / req_bytes);
|
|
|
|
unsigned char *buf_out = (unsigned char *) malloc((samples + 1) * out_bytes);
|
|
if (buf_out == NULL) {
|
|
fprintf(stderr, "Allocation of output buffer of %d samples of %d bits gives NULL", samples, output_bits);
|
|
*out_size = 0;
|
|
return NULL;
|
|
}
|
|
|
|
if (requested_bits == output_bits) {
|
|
*out_size = mem_size;
|
|
memcpy(buf_out, mem, mem_size);
|
|
} else {
|
|
// convert samples.
|
|
|
|
int sample;
|
|
unsigned char *p_in = (unsigned char *) mem;
|
|
unsigned char *p_out = buf_out;
|
|
|
|
for(sample = 0; sample < samples; sample++, p_in += req_bytes, p_out += out_bytes) {
|
|
register int32_t smpl;
|
|
smpl = read_sample(p_in, req_bytes, little_endian);
|
|
int32_t out_smpl = convert_bits(smpl, requested_bits, output_bits);
|
|
store_sample(p_out, out_smpl, out_bytes, little_endian);
|
|
}
|
|
|
|
*out_size = samples * out_bytes;
|
|
}
|
|
|
|
return buf_out;
|
|
}
|
|
|
|
void *ao_create_async(int bits, int rate, int channels, int byte_format, const char *wav_file_output)
|
|
{
|
|
init_ao();
|
|
|
|
AO_Handle *handle = (AO_Handle *) malloc(sizeof(AO_Handle));
|
|
if (handle == NULL) {
|
|
fprintf(stderr, "Cannot allocate ao_handle!\n");
|
|
return NULL;
|
|
}
|
|
|
|
int opened_bits = 0;
|
|
ao_device *dev = try_open_device(bits, rate, channels, byte_format, wav_file_output, &opened_bits);
|
|
|
|
if (dev == NULL) {
|
|
fprintf(stderr, "Cannot open ao-device, error code = %d\n", errno);
|
|
free(handle);
|
|
return NULL;
|
|
}
|
|
|
|
handle->ao_device = dev;
|
|
handle->requested_bits_per_sample = bits;
|
|
handle->dev_bits_per_sample = opened_bits;
|
|
handle->dev_channels = channels;
|
|
handle->dev_rate = rate;
|
|
handle->dev_endianess = byte_format;
|
|
handle->volume_in_10000 = 10000;
|
|
|
|
handle->play_queue = NULL;
|
|
handle->last_frame = NULL;
|
|
handle->at_second = -1;
|
|
handle->at_music_id = -1;
|
|
handle->music_duration = 0;
|
|
|
|
handle->buf_size = 0;
|
|
|
|
handle->paused = FALSE;
|
|
handle->stopped = FALSE;
|
|
|
|
#if defined(USE_PTHREADS) || defined(USE_DISPATCH)
|
|
pthread_mutex_init(&handle->clear_mutex, NULL);
|
|
pthread_mutex_init(&handle->mutex, NULL);
|
|
#ifdef USE_PTHREADS
|
|
sem_init(&handle->queue_sem, 0, 0);
|
|
sem_init(&handle->pause_sem, 0, 1);
|
|
#endif
|
|
#ifdef USE_DISPATCH
|
|
handle->queue_sem = dispatch_semaphore_create(0);
|
|
handle->pause_sem = dispatch_semaphore_create(1);
|
|
#endif
|
|
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->clear_mutex = CreateMutex(NULL,
|
|
FALSE,
|
|
NULL);
|
|
handle->queue_sem = CreateSemaphore(NULL, 0, 1000000, NULL);
|
|
handle->pause_sem = CreateSemaphore(NULL, 1, 1000000, NULL);
|
|
handle->thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) run, handle, 0, &handle->thread_id);
|
|
#endif
|
|
|
|
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");
|
|
|
|
MUTEX_LOCK(h->mutex);
|
|
h->stopped = TRUE;
|
|
MUTEX_UNLOCK(h->mutex);
|
|
|
|
clear(h, TRUE);
|
|
|
|
fprintf(stderr, "queue cleared\n");
|
|
|
|
MUTEX_LOCK(h->mutex);
|
|
if (h->paused) {
|
|
SEM_POST(h->pause_sem);
|
|
}
|
|
MUTEX_UNLOCK(h->mutex);
|
|
|
|
fprintf(stderr, "stop command queued\n");
|
|
|
|
#if defined(USE_PTHREADS) || defined(USE_DISPATCH)
|
|
void *retval;
|
|
pthread_join(h->thread, &retval);
|
|
|
|
#ifdef USE_PTHREADS
|
|
sem_destroy(&h->queue_sem);
|
|
sem_destroy(&h->pause_sem);
|
|
#endif
|
|
|
|
#ifdef USE_DISPATCH
|
|
dispatch_release(h->queue_sem);
|
|
dispatch_release(h->pause_sem);
|
|
#endif
|
|
|
|
pthread_mutex_destroy(&h->clear_mutex);
|
|
pthread_mutex_destroy(&h->mutex);
|
|
#endif
|
|
|
|
#ifdef USE_WINDOWS_THREADS
|
|
WaitForSingleObject(h->thread, INFINITE);
|
|
CloseHandle(h->thread);
|
|
CloseHandle(h->mutex);
|
|
CloseHandle(h->pause_sem);
|
|
CloseHandle(h->queue_sem);
|
|
CloseHandle(h->clear_mutex);
|
|
#endif
|
|
ao_close(h->ao_device);
|
|
|
|
fprintf(stderr, "device closed\n");
|
|
|
|
free(h);
|
|
|
|
fprintf(stderr, "async handle freed\n");
|
|
}
|
|
|
|
/*
|
|
static inline void make_sample_bytes(int32_t sample, int bytes_per_sample, int big_endian, unsigned char b[4])
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < bytes_per_sample; i++) {
|
|
b[i] = sample&0xff;
|
|
sample >>= 8;
|
|
}
|
|
if (big_endian) {
|
|
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 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;
|
|
|
|
if (stopped(h, TRUE)) { return; }
|
|
|
|
Queue_t *q = NULL;
|
|
|
|
int ao_size = 0;
|
|
void *ao_mem = convert_req_to_real(ao_handle, mem, buf_size, &info, &ao_size);
|
|
if (ao_mem == NULL || ao_size <= 0) {
|
|
if (ao_mem != NULL) { free(ao_mem); }
|
|
return;
|
|
}
|
|
|
|
q = new_elem(PLAY, music_id, at_second, music_duration, ao_size, ao_mem);
|
|
|
|
free(ao_mem);
|
|
|
|
if (q != NULL && q->buf != NULL) { // memory error has already been given.
|
|
add(h, q, FALSE);
|
|
}
|
|
}
|
|
|
|
void ao_clear_async(void *ao_handle)
|
|
{
|
|
AO_Handle *h = (AO_Handle *) ao_handle;
|
|
if (stopped(h, TRUE)) { return; }
|
|
clear(h, FALSE);
|
|
}
|
|
|
|
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_set_volume_async(void *ao_handle, double percentage)
|
|
{
|
|
AO_Handle *h = (AO_Handle *) ao_handle;
|
|
MUTEX_LOCK(h->mutex);
|
|
int volume_10000 = (int) (percentage * 100.0);
|
|
if (volume_10000 >= 9990 && volume_10000 <= 10010) {
|
|
volume_10000 = 10000;
|
|
}
|
|
h->volume_in_10000 = volume_10000;
|
|
MUTEX_UNLOCK(h->mutex);
|
|
}
|
|
|
|
|
|
double ao_volume_async(void *ao_handle)
|
|
{
|
|
AO_Handle *h = (AO_Handle *) ao_handle;
|
|
MUTEX_LOCK(h->mutex);
|
|
double volume = h->volume_in_10000 / 100.0;
|
|
MUTEX_UNLOCK(h->mutex);
|
|
return volume;
|
|
}
|
|
|
|
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;
|
|
SEM_POST(h->pause_sem);
|
|
}
|
|
} else {
|
|
if (paused) {
|
|
h->paused = paused;
|
|
SEM_WAIT_INFINITE(h->pause_sem);
|
|
}
|
|
}
|
|
|
|
MUTEX_UNLOCK(h->mutex);
|
|
}
|
|
|
|
int ao_real_output_bits_async(void *handle)
|
|
{
|
|
AO_Handle *h = (AO_Handle *) handle;
|
|
return h->dev_bits_per_sample;
|
|
}
|
|
|
|
|