changeset 4775:080cf3df3e4e

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
author atmos4
date Wed, 20 Feb 2002 22:45:48 +0000
parents fc2f76964606
children 47f0a86e8b8e
files libao2/ao_nas.c
diffstat 1 files changed, 302 insertions(+), 146 deletions(-) [+]
line wrap: on
line diff
--- 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;
 }