diff src/Output/sun/audio.c @ 0:13389e613d67 trunk

[svn] - initial import of audacious-plugins tree (lots to do)
author nenolod
date Mon, 18 Sep 2006 01:11:49 -0700
parents
children 088092a52fea
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Output/sun/audio.c	Mon Sep 18 01:11:49 2006 -0700
@@ -0,0 +1,586 @@
+/*
+ *  Copyright (C) 2001  CubeSoft Communications, Inc.
+ *  <http://www.csoft.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/* FIXME: g_error() is used several places, it will exit xmms */
+
+#include <errno.h>
+#include "libaudacious/util.h"
+#include "sun.h"
+#include "resample.h"
+
+static int sun_bps(int, int, int);
+static int sun_format(AFormat);
+
+static void sun_setformat(AFormat, int, int);
+static void sun_setparams(void);
+static void *sun_loop(void *);
+static int sun_downsample(gpointer, guint, guint, guint);
+
+static gboolean	prebuffer, remove_prebuffer;
+static pthread_t buffer_thread;
+static int (*sun_convert)(void **, int);
+static int realtime;
+static int rd_index, wr_index;
+static int buffer_size;
+static int prebuffer_size;
+static int output_time_offset;
+static int device_buffer_used;
+static int blocksize;
+static char *buffer;
+static guint64 output_bytes;
+static guint64 written;
+
+/*
+ * The format of the data from the input plugin
+ * This will never change during a song. 
+ */
+struct sun_format input;
+
+/*
+ * The format we get from the effect plugin.
+ * This will be different from input if the effect plugin does
+ * some kind of format conversion.
+ */
+struct sun_format effect;
+
+/*
+ * The format of the data we actually send to the soundcard.
+ * This might be different from effect if we need to resample or do
+ * some other format conversion.
+ */
+struct sun_format output;
+
+static int sun_bps(int sunfmt, int rate, int nch)
+{
+	int bitrate;
+
+	bitrate = rate * nch;
+
+	switch (sunfmt)
+	{
+		case AUDIO_ENCODING_ULINEAR_BE:
+		case AUDIO_ENCODING_ULINEAR_LE:
+		case AUDIO_ENCODING_SLINEAR_BE:
+		case AUDIO_ENCODING_SLINEAR_LE:
+			bitrate *= 2;
+			break;
+	}
+
+	return (bitrate);
+}
+
+static int sun_format(AFormat fmt)
+{
+	switch (fmt)
+	{
+		case FMT_U8:
+			return (AUDIO_ENCODING_PCM8);
+		case FMT_S8:
+			return (AUDIO_ENCODING_SLINEAR);
+		case FMT_U16_LE:
+			return (AUDIO_ENCODING_ULINEAR_LE);
+		case FMT_U16_BE:
+			return (AUDIO_ENCODING_ULINEAR_BE);
+		case FMT_U16_NE:
+#ifdef WORDS_BIGENDIAN
+			return (AUDIO_ENCODING_ULINEAR_BE);
+#else
+			return (AUDIO_ENCODING_ULINEAR_LE);
+#endif
+		case FMT_S16_LE:
+			return (AUDIO_ENCODING_SLINEAR_LE);
+		case FMT_S16_BE:
+			return (AUDIO_ENCODING_SLINEAR_BE);
+		case FMT_S16_NE:
+#ifdef WORDS_BIGENDIAN
+			return (AUDIO_ENCODING_SLINEAR_BE);
+#else
+			return (AUDIO_ENCODING_SLINEAR_LE);
+#endif
+	}
+	return -1;
+}
+
+static void sun_setformat(AFormat fmt, int rate, int nch)
+{
+	int sun;
+	
+	sun = sun_format(fmt);
+
+	effect.format.sun = sun;
+	effect.format.xmms = fmt;
+	effect.frequency = rate;
+	effect.channels = nch;
+	effect.bps = sun_bps(sun, rate, nch);
+
+	output.format.sun = sun;
+	output.format.xmms = fmt;
+	output.frequency = rate;
+	output.channels = nch;
+	sun_setparams();
+
+	output.bps = sun_bps(output.format.sun, output.frequency,
+			     output.channels);
+
+	audio.input = &input;
+	audio.output = &output;
+	audio.effect = &effect;
+}
+
+void sun_setparams(void)
+{
+	audio_info_t info;
+	audio_encoding_t enc;
+
+	AUDIO_INITINFO(&info);
+
+	info.mode = AUMODE_PLAY;
+	if (ioctl(audio.fd, AUDIO_SETINFO, &info) != 0)
+	{
+		g_error("%s: cannot play (%s)", audio.devaudio,
+			strerror(errno));
+		return;
+	}
+
+	/*
+	 * Pass 1: try the preferred encoding, if it is supported.
+	 */
+	enc.index = 0;
+	while (ioctl(audio.fd, AUDIO_GETENC, &enc) == 0 &&
+	       enc.encoding != output.format.sun)
+		enc.index++;
+
+	info.play.encoding = enc.encoding;
+	info.play.precision = enc.precision;
+	strcpy(output.name, enc.name);
+	if (ioctl(audio.fd, AUDIO_SETINFO, &info) != 0)
+	{
+		g_error("%s: unsupported encoding: %s (%s)", audio.devaudio,
+			output.name, strerror(errno));
+		return;
+	}
+
+	info.play.channels = output.channels;
+	ioctl(audio.fd, AUDIO_SETINFO, &info);
+
+	info.play.sample_rate = output.frequency;
+	if (ioctl(audio.fd, AUDIO_SETINFO, &info) < 0)
+	{
+		g_error("%s: cannot handle %i Hz (%s)", audio.devaudio,
+			output.frequency, strerror(errno));
+		return;
+	}
+
+	if (ioctl(audio.fd, AUDIO_GETINFO, &info) != 0)
+	{
+		blocksize = SUN_DEFAULT_BLOCKSIZE;
+		output.channels = info.play.channels;
+	}
+	else
+	{
+		blocksize = blocksize;
+	}
+
+	sun_convert = sun_get_convert_func(output.format.sun,
+					   sun_format(effect.format.xmms));
+#if 0
+	if (sun_convert != NULL)
+	{
+		g_warning("audio conversion (output=0x%x effect=0x%x)",
+		    output.format.sun, sun_format(effect.format.xmms));
+	}
+#endif
+}
+
+static inline void sun_bufused(void)
+{
+	audio_offset_t ooffs;
+
+	if (audio.paused)
+		device_buffer_used = 0;
+	else if (ioctl(audio.fd, AUDIO_GETOOFFS, &ooffs) == 0)
+		device_buffer_used = ooffs.offset;
+}
+
+int sun_written_time(void)
+{
+	if (!audio.going)
+		return 0;
+
+	return ((written * 1000) / effect.bps);
+}
+
+int sun_output_time(void)
+{
+	guint64 bytes;
+
+	if (!audio.fd || !audio.going)
+		return 0;
+
+	if (realtime)
+		sun_bufused();
+
+	bytes = output_bytes < device_buffer_used ?
+		0 : output_bytes - device_buffer_used;
+	return (output_time_offset + ((bytes * 1000) / output.bps));
+}
+
+static inline int sun_used(void)
+{
+	if (realtime)
+		return 0;
+	
+	if (wr_index >= rd_index)
+		return (wr_index - rd_index);
+
+	return (buffer_size - (rd_index - wr_index));
+}
+
+int sun_playing(void)
+{
+	if (!audio.going)
+		return 0;
+
+	if (realtime)
+		sun_bufused();
+
+	if (!sun_used() && (device_buffer_used - (3 * blocksize)) <= 0)
+		return (FALSE);
+
+	return (TRUE);
+}
+
+int sun_free(void)
+{
+	if (realtime)
+		return (audio.paused ? 0 : 1000000);
+	
+	if (remove_prebuffer && prebuffer)
+	{
+		prebuffer = FALSE;
+		remove_prebuffer = FALSE;
+	}
+	if (prebuffer)
+		remove_prebuffer = TRUE;
+
+	if (rd_index > wr_index)
+		return ((rd_index - wr_index) - blocksize - 1);
+
+	return ((buffer_size - (wr_index - rd_index)) - blocksize - 1);
+}
+
+static inline ssize_t write_all(int fd, const void *buf, size_t count)
+{
+	static ssize_t done;
+
+	for (done = 0; count > done; )
+	{
+		static ssize_t n;
+
+		n = write(fd, buf, count - done);
+		if (n == -1)
+		{
+			if (errno == EINTR)
+				continue;
+			else
+				break;
+		}
+		done += n;
+	}
+
+	return (done);
+}
+
+static inline void sun_write_audio(gpointer data, int length)
+{
+	AFormat new_format;
+	EffectPlugin *ep;
+	int new_frequency, new_channels;
+
+	new_format = input.format.xmms;
+	new_frequency = input.frequency;
+	new_channels = input.channels;
+
+	ep = get_current_effect_plugin();
+	if (effects_enabled() && ep && ep->query_format)
+		ep->query_format(&new_format, &new_frequency, &new_channels);
+
+	if (new_format != effect.format.xmms || 
+	    new_frequency != effect.frequency ||
+	    new_channels != effect.channels)
+	{
+		output_time_offset += (output_bytes * 1000) / output.bps;
+		output_bytes = 0;
+		close(audio.fd);
+		audio.fd = open(audio.devaudio, O_RDWR);
+		sun_setformat(new_format, new_frequency, new_channels);
+	}
+	if (effects_enabled() && ep && ep->mod_samples)
+	{
+		length = ep->mod_samples(&data, length, input.format.xmms,
+					 input.frequency, input.channels);
+	}
+
+	if (sun_convert != NULL)
+		length = sun_convert(&data, length);
+
+	if (effect.frequency == output.frequency)
+	{
+		output_bytes += write_all(audio.fd, data, length);
+	}
+	else
+	{
+		output_bytes += sun_downsample(data, length,
+		    effect.frequency, output.frequency);
+	}
+}
+
+static void sun_bswap16(guint16 *data, int len)
+{
+	int i;
+
+	for (i = 0; i < len; i += 2, data++)
+		*data = GUINT16_SWAP_LE_BE(*data);
+}
+
+static int sun_downsample(gpointer ob, guint length, guint speed, guint espeed)
+{
+	guint w = 0;
+	static gpointer nbuffer = NULL;
+	static int nbuffer_size = 0;
+
+	switch (output.format.sun) {
+		case AUDIO_ENCODING_SLINEAR_BE:
+		case AUDIO_ENCODING_SLINEAR_LE:
+			if (output.channels == 2)
+				RESAMPLE_STEREO(gint16);
+			else
+				RESAMPLE_MONO(gint16);
+			break;
+		case AUDIO_ENCODING_ULINEAR_BE:
+		case AUDIO_ENCODING_ULINEAR_LE:
+			if (output.channels == 2)
+				RESAMPLE_STEREO(guint16);
+			else
+				RESAMPLE_MONO(guint16);
+			break;
+		case AUDIO_ENCODING_SLINEAR:
+			if (output.channels == 2)
+				RESAMPLE_STEREO(gint8);
+			else
+				RESAMPLE_MONO(gint8);
+			break;
+		case AUDIO_ENCODING_ULINEAR:
+			if (output.channels == 2)
+				RESAMPLE_STEREO(guint8);
+			else
+				RESAMPLE_MONO(guint8);
+			break;
+	}
+	return (w);
+}
+
+void sun_write(gpointer ptr, int length)
+{
+	int cnt, off = 0;
+
+	if (realtime)
+	{
+		if (audio.paused)
+			return;
+		sun_write_audio(ptr, length);
+		written += length;
+		return;
+	}
+
+	remove_prebuffer = FALSE;
+	written += length;
+	while (length > 0)
+	{
+		cnt = MIN(length, buffer_size - wr_index);
+		memcpy(buffer + wr_index, (char *) ptr + off, cnt);
+		wr_index = (wr_index + cnt) % buffer_size;
+		length -= cnt;
+		off += cnt;
+	}
+}
+
+void sun_close(void)
+{
+	if (!audio.going)
+		return;
+
+	audio.going = 0;
+
+	if (realtime)
+	{
+		ioctl(audio.fd, AUDIO_FLUSH, NULL);
+		close(audio.fd);
+	}
+	else
+	{
+		pthread_join(buffer_thread, NULL);
+	}
+
+	sun_get_convert_buffer(0);
+	wr_index = 0;
+	rd_index = 0;
+}
+
+void sun_flush(int time)
+{
+	ioctl(audio.fd, AUDIO_FLUSH, NULL);
+
+	output_time_offset = time;
+	written = (guint16)(time / 10) * (guint64)(input.bps / 100);
+	output_bytes = 0;
+}
+
+void sun_pause(short p)
+{
+	if (!realtime)
+	{
+		if (p == TRUE)
+			audio.do_pause = TRUE;
+		else
+			audio.unpause = TRUE;
+	}
+	else
+		audio.paused = p;
+}
+
+static void* sun_loop(void *arg)
+{
+	struct timeval tv;
+	int length, cnt;
+	fd_set set;
+
+	while (audio.going)
+	{
+		if (sun_used() > prebuffer_size)
+			prebuffer = FALSE;
+
+		if (sun_used() > 0 && !audio.paused && !prebuffer)
+		{
+			tv.tv_sec = 0;
+			tv.tv_usec = 10000;
+			FD_ZERO(&set);
+			FD_SET(audio.fd, &set);
+
+			if (select(audio.fd + 1, NULL, &set, NULL, &tv) > 0)
+			{
+				length = MIN(blocksize, sun_used());
+				while (length > 0)
+				{
+					cnt = MIN(length,
+					    buffer_size - rd_index);
+					sun_write_audio(
+					    buffer + rd_index, cnt);
+					rd_index = (rd_index + cnt) %
+					    buffer_size;
+					length -= cnt;
+				}
+			}
+		}
+		else
+			xmms_usleep(10000);
+
+		sun_bufused();
+
+		if (audio.do_pause && !audio.paused)
+		{
+			audio.do_pause = FALSE;
+			audio.paused = TRUE;
+
+			rd_index -= device_buffer_used;
+			output_bytes -= device_buffer_used;
+			if (rd_index < 0)
+				rd_index += buffer_size;
+			ioctl(audio.fd, AUDIO_FLUSH, NULL);
+		}
+		else if (audio.unpause && audio.paused)
+		{
+			audio.unpause = FALSE;
+			close(audio.fd);
+			audio.fd = open(audio.devaudio, O_RDWR);
+			sun_setparams();
+			audio.paused = FALSE;
+		}
+	}
+
+	close(audio.fd);
+	g_free(buffer);
+	pthread_exit(NULL);
+}
+
+int sun_open(AFormat fmt, int rate, int nch)
+{
+	audio_info_t info;
+
+	AUDIO_INITINFO(&info);
+
+	if ((audio.fd = open(audio.devaudio, O_RDWR)) < 0)
+	{
+		g_error("%s: %s", audio.devaudio, strerror(errno));
+		return 0;
+	}
+
+	input.format.xmms = fmt;
+	input.frequency = rate;
+	input.channels = nch;
+	input.bps = sun_bps(sun_format(fmt), rate, nch);
+	sun_setformat(fmt, rate, nch);
+
+	realtime = xmms_check_realtime_priority();
+
+	if (ioctl(audio.fd, AUDIO_GETINFO, &info) != 0)
+		blocksize = SUN_DEFAULT_BLOCKSIZE;
+	else
+		blocksize = info.blocksize;
+
+	if (!realtime)
+	{
+		buffer_size = audio.req_buffer_size;
+
+		if (buffer_size < SUN_MIN_BUFFER_SIZE)
+			buffer_size = SUN_MIN_BUFFER_SIZE;
+
+		prebuffer_size = (buffer_size * audio.req_prebuffer_size) / 100;
+
+		buffer_size += blocksize;
+		buffer = g_malloc0(buffer_size);
+	}
+	prebuffer = TRUE;
+	wr_index = 0;
+	rd_index = 0;
+	output_time_offset = 0;
+	written = 0;
+	output_bytes = 0;
+
+	audio.paused = FALSE;
+	audio.do_pause = FALSE;
+	audio.unpause = FALSE;
+	remove_prebuffer = FALSE;
+
+	audio.going++;
+	if (!realtime)
+		pthread_create(&buffer_thread, NULL, sun_loop, NULL);
+
+	return 1;
+}