#include "ao_playasync.h" #include "../ffi_version.h" #ifdef WIN32 #include #define USE_WINDOWS_THREADS #define sleep_ms(ms) Sleep(ms) #else #ifdef __APPLE__ #define USE_DISPATCH #include #define sleep_ms(ms) msleep(ms) #else #define USE_PTHREADS #include #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 #include #include 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_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_POST(sem) ReleaseSemaphore(sem, 1, NULL) #define YIELD() sleep_ms(5) #endif #ifdef USE_PTHREADS #include #include #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_TRYWAIT(sem) (sem_trywait(&sem) == 0) #define SEM_POST(sem) sem_post(&sem) #define YIELD() yield() #endif #ifndef WIN32 #include #endif #ifndef __APPLE__ #include #endif #include #include #include #include #include #include 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; 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 pause_mutex; HANDLE clear_mutex; HANDLE thread; DWORD thread_id; HANDLE queue_sem; #endif #if defined(USE_PTHREADS) || defined(USE_DISPATCH) pthread_mutex_t mutex; pthread_mutex_t pause_mutex; pthread_mutex_t clear_mutex; pthread_t thread; #ifdef USE_PTHREADS sem_t queue_sem; #endif #ifdef USE_DISPATCH dispatch_semaphore_t queue_sem; #endif #endif double at_second; double music_duration; int at_music_id; int buf_size; int volume_in_10000; // volume in 100000 steps, i.e. 100000 equals 100% } 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); } 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; } } } } #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) { 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 (handle->volume_in_10000 != 10000) { // adjust volume adjustVolume(handle, q->buf, q->buflen, handle->volume_in_10000); } if (q->command == STOP) { go_on = (1 == 0); } else { ao_play(handle->ao_device, (char *) q->buf, q->buflen); } 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 _WIN32 #else #include #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); 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 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 sample, int out_bytes, int is_little_endian) { 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 void *convert_req_to_real(AO_Handle *h, void *mem, int mem_size, BufferInfo_t *info, int *out_size) { 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 = 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 * 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)); ao_sample_format fmt; fmt.bits = bits; fmt.rate = rate; fmt.byte_format = byte_format; fmt.channels = channels; fmt.matrix = 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->buf_size = 0; handle->paused = (1 == 0); #if defined(USE_PTHREADS) || defined(USE_DISPATCH) 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; #ifdef USE_PTHREADS sem_init(&handle->queue_sem, 0, 0); #endif #ifdef USE_DISPATCH handle->queue_sem = dispatch_semaphore_create(0); #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->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"); #if defined(USE_PTHREADS) || defined(USE_DISPATCH) void *retval; pthread_join(h->thread, &retval); #ifdef USE_PTHREADS sem_destroy(&h->queue_sem); #endif pthread_mutex_destroy(&h->pause_mutex); 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); #endif ao_close(h->ao_device); fprintf(stderr, "device closed\n"); free(h); fprintf(stderr, "async handle freed\n"); } #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 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; Queue_t *q = NULL; int ao_size = 0; void *ao_mem = convert_req_to_real(ao_handle, mem, buf_size, &info, &ao_size); q = new_elem(PLAY, music_id, at_second, music_duration, ao_size, ao_mem); free(ao_mem); 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_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; MUTEX_UNLOCK(h->pause_mutex); } } else { if (paused) { h->paused = paused; MUTEX_LOCK(h->pause_mutex); } } MUTEX_UNLOCK(h->mutex); } int ao_real_output_bits_async(void *handle) { AO_Handle *h = (AO_Handle *) handle; return h->dev_bits_per_sample; }