# HG changeset patch # User atmos4 # Date 1014245148 0 # Node ID 080cf3df3e4ef3937622ffe7b795f389a25a1322 # Parent fc2f769646067671611483672ab75491e89de7da improved event handling, implemented working pause that does not flush all buffers, work around a deadlock in the new threadsafe version 1.5 of libaudio by Tobias Diedrich diff -r fc2f76964606 -r 080cf3df3e4e libao2/ao_nas.c --- a/libao2/ao_nas.c Wed Feb 20 22:45:00 2002 +0000 +++ b/libao2/ao_nas.c Wed Feb 20 22:45:48 2002 +0000 @@ -21,13 +21,70 @@ #include "audio_out_internal.h" #include "afmt.h" -#define FRAG_SIZE 4096 -#define FRAG_COUNT 8 -#define BUFFER_SIZE FRAG_SIZE * FRAG_COUNT +#define NAS_FRAG_SIZE 4096 +#define NAS_FRAG_COUNT 8 +#define NAS_BUFFER_SIZE NAS_FRAG_SIZE * NAS_FRAG_COUNT #define NAS_DEBUG 0 #if NAS_DEBUG == 1 + +static char *nas_event_types[] = { + "Undefined", + "Undefined", + "ElementNotify", + "GrabNotify", + "MonitorNotify", + "BucketNotify", + "DeviceNotify" +}; + +static char *nas_elementnotify_kinds[] = { + "LowWater", + "HighWater", + "State", + "Unknown" +}; + +static char *nas_states[] = { + "Stop", + "Start", + "Pause", + "Any" +}; + +static char *nas_reasons[] = { + "User", + "Underrun", + "Overrun", + "EOF", + "Watermark", + "Hardware", + "Any" +}; + +static char* nas_reason(unsigned int reason) +{ + if (reason > 6) reason = 6; + return nas_reasons[reason]; +} + +static char* nas_elementnotify_kind(unsigned int kind) +{ + if (kind > 2) kind = 3; + return nas_elementnotify_kinds[kind]; +} + +static char* nas_event_type(unsigned int type) { + if (type > 6) type = 0; + return nas_event_types[type]; +} + +static char* nas_state(unsigned int state) { + if (state>3) state = 3; + return nas_states[state]; +} + #define DPRINTF(format, args...) fprintf(stderr, format, ## args); \ fflush(stderr) #else @@ -42,109 +99,115 @@ "" }; -static AuServer* aud; -static AuFlowID flow; -static AuDeviceID dev; +struct ao_nas_data { + AuServer *aud; + AuFlowID flow; + AuDeviceID dev; + + int flow_stopped; + int flow_paused; -static void *client_buffer; -static int client_buffer_size = BUFFER_SIZE; -static int client_buffer_used; -static int server_buffer_size = BUFFER_SIZE; -static int server_buffer_used; -static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER; + void *client_buffer; + int client_buffer_size; + int client_buffer_used; + int server_buffer_size; + int server_buffer_used; + pthread_mutex_t buffer_mutex; -pthread_t event_thread; -static int stop_thread; + pthread_t event_thread; + int stop_thread; +}; + +static struct ao_nas_data *nas_data; LIBAO_EXTERN(nas) -static void wait_for_event() +static void nas_print_error(AuServer *aud, char *prefix, AuStatus as) { - AuEvent ev; - - AuNextEvent(aud, AuTrue, &ev); - AuDispatchEvent(aud, &ev); + char s[100]; + AuGetErrorText(aud, as, s, 100); + fprintf(stderr, "ao_nas: %s: returned status %d (%s)\n", prefix, as, s); + fflush(stderr); } -static int readBuffer(int num) +static int nas_readBuffer(struct ao_nas_data *nas_data, int num) { - pthread_mutex_lock(&buffer_mutex); - DPRINTF("readBuffer(): num=%d client=%d/%d server=%d/%d\n", + AuStatus as; + + pthread_mutex_lock(&nas_data->buffer_mutex); + DPRINTF("ao_nas: nas_readBuffer(): num=%d client=%d/%d server=%d/%d\n", num, - client_buffer_used, client_buffer_size, - server_buffer_used, server_buffer_size); + nas_data->client_buffer_used, nas_data->client_buffer_size, + nas_data->server_buffer_used, nas_data->server_buffer_size); - if (client_buffer_used == 0) { - DPRINTF("buffer is empty, nothing read.\n"); - pthread_mutex_unlock(&buffer_mutex); + if (nas_data->client_buffer_used == 0) { + DPRINTF("ao_nas: buffer is empty, nothing read.\n"); + pthread_mutex_unlock(&nas_data->buffer_mutex); return 0; } - if (client_buffer_used < num) - num = client_buffer_used; + if (nas_data->client_buffer_used < num) + num = nas_data->client_buffer_used; - AuWriteElement(aud, flow, 0, num, client_buffer, AuFalse, NULL); - client_buffer_used -= num; - server_buffer_used += num; - memmove(client_buffer, client_buffer + num, client_buffer_used); - pthread_mutex_unlock(&buffer_mutex); + AuWriteElement(nas_data->aud, nas_data->flow, 0, num, nas_data->client_buffer, AuFalse, &as); + if (as != AuSuccess) + nas_print_error(nas_data->aud, "nas_readBuffer(): AuWriteElement", as); + else { + nas_data->client_buffer_used -= num; + nas_data->server_buffer_used += num; + memmove(nas_data->client_buffer, nas_data->client_buffer + num, nas_data->client_buffer_used); + } + pthread_mutex_unlock(&nas_data->buffer_mutex); + + if (nas_data->flow_paused) { + AuPauseFlow(nas_data->aud, nas_data->flow, &as); + if (as != AuSuccess) + nas_print_error(nas_data->aud, "nas_readBuffer(): AuPauseFlow", as); + } return num; } -static void writeBuffer(void *data, int len) +static void nas_writeBuffer(struct ao_nas_data *nas_data, void *data, int len) { - pthread_mutex_lock(&buffer_mutex); - DPRINTF("writeBuffer(): len=%d client=%d/%d server=%d/%d\n", - len, client_buffer_used, client_buffer_size, - server_buffer_used, server_buffer_size); - - memcpy(client_buffer + client_buffer_used, data, len); - client_buffer_used += len; + pthread_mutex_lock(&nas_data->buffer_mutex); + DPRINTF("ao_nas: nas_writeBuffer(): len=%d client=%d/%d server=%d/%d\n", + len, nas_data->client_buffer_used, nas_data->client_buffer_size, + nas_data->server_buffer_used, nas_data->server_buffer_size); - pthread_mutex_unlock(&buffer_mutex); - if (server_buffer_used < server_buffer_size) - readBuffer(server_buffer_size - server_buffer_used); -} - + memcpy(nas_data->client_buffer + nas_data->client_buffer_used, data, len); + nas_data->client_buffer_used += len; -static void *event_thread_start(void *data) -{ - while (!stop_thread) { - wait_for_event(); - } + pthread_mutex_unlock(&nas_data->buffer_mutex); + if (nas_data->server_buffer_used < nas_data->server_buffer_size) + nas_readBuffer(nas_data, nas_data->server_buffer_size - nas_data->server_buffer_used); } -static AuBool event_handler(AuServer *aud, AuEvent *ev, AuEventHandlerRec *hnd) +static int nas_empty_event_queue(struct ao_nas_data *nas_data) { - switch (ev->type) { - case AuEventTypeElementNotify: { - AuElementNotifyEvent *event = (AuElementNotifyEvent *) ev; - DPRINTF("event_handler(): kind %d state %d->%d reason %d numbytes %d\n", - event->kind, - event->prev_state, - event->cur_state, - event->reason, - event->num_bytes); - - switch (event->kind) { - case AuElementNotifyKindLowWater: - server_buffer_used -= event->num_bytes; - readBuffer(event->num_bytes); - break; - case AuElementNotifyKindState: - if ((event->cur_state == AuStatePause) && - (event->reason != AuReasonUser)) { - // buffer underrun -> refill buffer - server_buffer_used = 0; - readBuffer(server_buffer_size - server_buffer_used); - } - } - } + AuEvent ev; + int result = 0; + + while (AuScanForTypedEvent(nas_data->aud, AuEventsQueuedAfterFlush, + AuTrue, AuEventTypeElementNotify, &ev)) { + AuDispatchEvent(nas_data->aud, &ev); + result = 1; } - return AuTrue; + return result; } -static AuBool error_handler(AuServer* aud, AuErrorEvent* ev) +static void *nas_event_thread_start(void *data) +{ + struct ao_nas_data *nas_data = data; + AuEvent ev; + AuBool result; + + do { + nas_empty_event_queue(nas_data); + usleep(10000); + } while (!nas_data->stop_thread); +} + +static AuBool nas_error_handler(AuServer* aud, AuErrorEvent* ev) { char s[100]; AuGetErrorText(aud, ev->error_code, s, 100); @@ -161,7 +224,54 @@ return AuTrue; } -static AuDeviceID find_device(int nch) +static AuBool nas_event_handler(AuServer *aud, AuEvent *ev, AuEventHandlerRec *hnd) +{ + AuElementNotifyEvent *event = (AuElementNotifyEvent *) ev; + AuStatus as; + struct ao_nas_data *nas_data = hnd->data; + + switch (ev->type) { + case AuEventTypeElementNotify: + DPRINTF("ao_nas: event_handler(): kind %s state %s->%s reason %s numbytes %d\n", + nas_elementnotify_kind(event->kind), + nas_state(event->prev_state), + nas_state(event->cur_state), + nas_reason(event->reason), + event->num_bytes); + + nas_data->server_buffer_used -= event->num_bytes; + if (nas_data->server_buffer_used < 0) + nas_data->server_buffer_used = 0; + + switch (event->kind) { + case AuElementNotifyKindLowWater: + nas_readBuffer(nas_data, event->num_bytes); + break; + case AuElementNotifyKindState: + if (event->cur_state == AuStatePause) { + switch (event->reason) { + case AuReasonUnderrun: + // buffer underrun -> refill buffer + nas_data->server_buffer_used = 0; + nas_readBuffer(nas_data, nas_data->server_buffer_size - nas_data->server_buffer_used); + break; + default: + break; + } + } + break; + default: // silently ignored + break; + } + break; + default: + printf("ao_nas: nas_event_handler(): unhandled event type %d\n", ev->type); + break; + } + return AuTrue; +} + +static AuDeviceID nas_find_device(AuServer *aud, int nch) { int i; for (i = 0; i < AuServerNumDevices(aud); i++) { @@ -174,7 +284,7 @@ return AuNone; } -static unsigned char aformat_to_auformat(unsigned int format) +static unsigned char nas_aformat_to_auformat(unsigned int format) { switch (format) { case AFMT_U8: return AuFormatLinearUnsigned8; @@ -199,29 +309,32 @@ { AuElement elms[3]; AuStatus as; - unsigned char auformat = aformat_to_auformat(format); - int bytes_per_sample; + unsigned char auformat = nas_aformat_to_auformat(format); + int bytes_per_sample = channels * AuSizeofFormat(auformat); char *server; + nas_data=malloc(sizeof(struct ao_nas_data)); + printf("ao2: %d Hz %d chans %s\n",rate,channels, - audio_out_format_name(format)); + audio_out_format_name(format)); if (!auformat) { - printf("Unsupported format -> nosound\n"); + printf("ao_nas: init(): Unsupported format -> nosound\n"); return 0; } - client_buffer = malloc(BUFFER_SIZE); - bytes_per_sample = channels * AuSizeofFormat(auformat); + nas_data->client_buffer_size = NAS_BUFFER_SIZE; + nas_data->client_buffer = malloc(nas_data->client_buffer_size); + nas_data->server_buffer_size = NAS_BUFFER_SIZE; ao_data.samplerate = rate; ao_data.channels = channels; - ao_data.buffersize = BUFFER_SIZE * 2; - ao_data.outburst = FRAG_SIZE; + ao_data.buffersize = NAS_BUFFER_SIZE * 2; + ao_data.outburst = NAS_FRAG_SIZE; ao_data.bps = rate * bytes_per_sample; if (!bytes_per_sample) { - printf("Zero bytes per sample -> nosound\n"); + printf("ao_nas: init(): Zero bytes per sample -> nosound\n"); return 0; } @@ -231,95 +344,112 @@ if (!server) // default to tcp/localhost:8000 server = "tcp/localhost:8000"; - printf("Using audioserver %s\n", server); + printf("ao_nas: init(): Using audioserver %s\n", server); - aud = AuOpenServer(server, 0, NULL, 0, NULL, NULL); - if (!aud){ - printf("Can't open nas audio server -> nosound\n"); + nas_data->aud = AuOpenServer(server, 0, NULL, 0, NULL, NULL); + if (!nas_data->aud){ + printf("ao_nas: init(): Can't open nas audio server -> nosound\n"); return 0; } - dev = find_device(channels); - if ((dev == AuNone) || (!(flow = AuCreateFlow(aud, NULL)))) { - printf("Can't find a device serving that many channels -> nosound\n"); - AuCloseServer(aud); - aud = 0; + nas_data->dev = nas_find_device(nas_data->aud, channels); + if ((nas_data->dev == AuNone) || (!(nas_data->flow = AuCreateFlow(nas_data->aud, NULL)))) { + printf("ao_nas: init(): Can't find a device serving that many channels -> nosound\n"); + AuCloseServer(nas_data->aud); + nas_data->aud = 0; return 0; } AuMakeElementImportClient(elms, rate, auformat, channels, AuTrue, - BUFFER_SIZE / bytes_per_sample, - (BUFFER_SIZE - FRAG_SIZE) / bytes_per_sample, + NAS_BUFFER_SIZE / bytes_per_sample, + (NAS_BUFFER_SIZE - NAS_FRAG_SIZE) / bytes_per_sample, 0, NULL); - AuMakeElementExportDevice(elms+1, 0, dev, rate, + AuMakeElementExportDevice(elms+1, 0, nas_data->dev, rate, AuUnlimitedSamples, 0, NULL); - AuSetElements(aud, flow, AuTrue, 2, elms, &as); + AuSetElements(nas_data->aud, nas_data->flow, AuTrue, 2, elms, &as); if (as != AuSuccess) - printf("AuSetElements returned status %d!\n", as); - AuRegisterEventHandler(aud, AuEventHandlerIDMask | + nas_print_error(nas_data->aud, "init(): AuSetElements", as); + AuRegisterEventHandler(nas_data->aud, AuEventHandlerIDMask | AuEventHandlerTypeMask, - AuEventTypeElementNotify, flow, - event_handler, (AuPointer) NULL); - AuSetErrorHandler(aud, error_handler); - AuStartFlow(aud, flow, &as); - if (as != AuSuccess) - printf("AuSetElements returned status %d!\n", as); + AuEventTypeElementNotify, nas_data->flow, + nas_event_handler, (AuPointer) nas_data); + AuSetErrorHandler(nas_data->aud, nas_error_handler); + nas_data->flow_stopped=1; - /* - * Wait for first buffer underrun event - * - * For some weird reason we get a buffer underrun event if we - * don't fill the server buffer fast enough after staring the - * flow. So we just wait for it to happen to be in a sane state. - */ - wait_for_event(); - - pthread_create(&event_thread, NULL, &event_thread_start, NULL); + pthread_mutex_init(&nas_data->buffer_mutex, NULL); + pthread_create(&nas_data->event_thread, NULL, &nas_event_thread_start, nas_data); return 1; } // close audio device static void uninit(){ - stop_thread = 1; - pthread_join(event_thread, NULL); - AuStopFlow(aud, flow, NULL); - AuCloseServer(aud); - aud = 0; - free(client_buffer); + AuStatus as; + + nas_data->stop_thread = 1; + pthread_join(nas_data->event_thread, NULL); + if (!nas_data->flow_stopped) { + AuStopFlow(nas_data->aud, nas_data->flow, &as); + if (as != AuSuccess) + nas_print_error(nas_data->aud, "uninit(): AuStopFlow", as); + } + AuCloseServer(nas_data->aud); + nas_data->aud = 0; + free(nas_data->client_buffer); } // stop playing and empty buffers (for seeking/pause) static void reset(){ - pthread_mutex_lock(&buffer_mutex); - client_buffer_used = 0; - pthread_mutex_unlock(&buffer_mutex); - while (server_buffer_used > 0) { - usleep(1000); -// DPRINTF("used=%d\n", server_buffer_used); + AuStatus as; + + pthread_mutex_lock(&nas_data->buffer_mutex); + nas_data->client_buffer_used = 0; + if (!nas_data->flow_stopped) { + AuStopFlow(nas_data->aud, nas_data->flow, &as); + if (as != AuSuccess) + nas_print_error(nas_data->aud, "reset(): AuStopFlow", as); + nas_data->flow_stopped = 1; } + nas_data->server_buffer_used = 0; + pthread_mutex_unlock(&nas_data->buffer_mutex); } // stop playing, keep buffers (for pause) static void audio_pause() { - // for now, just call reset(); - reset(); + AuStatus as; + + DPRINTF("ao_nas: audio_pause()\n"); + + nas_data->flow_paused = 1; } // resume playing, after audio_pause() static void audio_resume() { + AuStatus as; + AuEvent ev; + + DPRINTF("ao_nas: audio_resume()\n"); + + nas_data->flow_stopped = 0; + nas_data->flow_paused = 0; + AuStartFlow(nas_data->aud, nas_data->flow, &as); + if (as != AuSuccess) + nas_print_error(nas_data->aud, "play(): AuStartFlow", as); } + // return: how many bytes can be played without blocking static int get_space() { int result; + + DPRINTF("ao_nas: get_space()\n"); - pthread_mutex_lock(&buffer_mutex); - result = client_buffer_size - client_buffer_used; - pthread_mutex_unlock(&buffer_mutex); + pthread_mutex_lock(&nas_data->buffer_mutex); + result = nas_data->client_buffer_size - nas_data->client_buffer_used; + pthread_mutex_unlock(&nas_data->buffer_mutex); return result; } @@ -327,21 +457,47 @@ // plays 'len' bytes of 'data' // it should round it down to outburst*n // return: number of bytes played -static int play(void* data,int len,int flags){ - writeBuffer(data, len); -// printf("ao_nas: wrote %d bytes of audio data\n", len); - return len; +static int play(void* data,int len,int flags) +{ + int maxbursts, playbursts, writelen; + AuStatus as; + + DPRINTF("ao_nas: play()\n"); + + if (nas_data->flow_stopped) { + AuEvent ev; + + AuStartFlow(nas_data->aud, nas_data->flow, &as); + if (as != AuSuccess) + nas_print_error(nas_data->aud, "play(): AuStartFlow", as); + nas_data->flow_stopped = 0; + while (!nas_empty_event_queue(nas_data)); // wait for first buffer underrun event + } + + pthread_mutex_lock(&nas_data->buffer_mutex); + maxbursts = (nas_data->client_buffer_size - + nas_data->client_buffer_used) / ao_data.outburst; + playbursts = len / ao_data.outburst; + writelen = (playbursts > maxbursts ? maxbursts : playbursts) * + ao_data.outburst; + pthread_mutex_unlock(&nas_data->buffer_mutex); + + nas_writeBuffer(nas_data, data, writelen); + return writelen; } // return: delay in seconds between first and last sample in buffer static float get_delay() { float result; + + DPRINTF("ao_nas: get_delay()\n"); - pthread_mutex_lock(&buffer_mutex); - result = ((float)(client_buffer_used + server_buffer_used)) / - (float)ao_data.bps; - pthread_mutex_unlock(&buffer_mutex); + pthread_mutex_lock(&nas_data->buffer_mutex); + result = ((float)(nas_data->client_buffer_used + + nas_data->server_buffer_used)) / + (float)ao_data.bps; + pthread_mutex_unlock(&nas_data->buffer_mutex); return result; }