Mercurial > audlegacy-plugins
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; +}