diff src/OSS4/audio.c @ 1214:2a722c3ccd9e

damn, I forgot to 'hg add' the sources Enjoy OSS4!
author Cristi Magherusan <majeru@atheme-project.org>
date Sat, 07 Jul 2007 05:02:14 +0300
parents
children cc04ccffaa8d
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/OSS4/audio.c	Sat Jul 07 05:02:14 2007 +0300
@@ -0,0 +1,761 @@
+/*  BMP - Cross-platform multimedia player
+ *  Copyright (C) 2003-2004  BMP development team.
+ *
+ *  Based on XMMS:
+ *  Copyright (C) 1998-2003  XMMS development team.
+ *
+ *  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.
+ */
+
+extern void close_mixer_device();
+
+#include <glib.h>
+#include <audacious/util.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+
+#include "OSS4.h"
+
+
+#define NFRAGS		32
+
+static gint fd = 0;
+static char *buffer;
+static gboolean going, prebuffer, paused, unpause, do_pause, remove_prebuffer;
+static gint device_buffer_used, buffer_size, prebuffer_size, blk_size;
+static gint rd_index = 0, wr_index = 0;
+static gint output_time_offset = 0;
+static guint64 written = 0, output_bytes = 0;
+static gint flush;
+static gint fragsize, device_buffer_size;
+static gchar *device_name;
+static GThread *buffer_thread;
+static gboolean realtime, select_works;
+
+static int (*oss_convert_func) (void **data, int length);
+static int (*oss_stereo_convert_func) (void **data, int length, int fmt);
+
+struct format_info {
+    union {
+        AFormat xmms;
+        int oss;
+    } format;
+    int frequency;
+    int channels;
+    int bps;
+};
+
+
+/*
+ * The format of the data from the input plugin
+ * This will never change during a song. 
+ */
+struct format_info 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 format_info 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 format_info output;
+
+
+static void
+oss_calc_device_buffer_used(void)
+{
+    audio_buf_info buf_info;
+    if (paused)
+        device_buffer_used = 0;
+    else if (!ioctl(fd, SNDCTL_DSP_GETOSPACE, &buf_info))
+        device_buffer_used =
+            (buf_info.fragstotal * buf_info.fragsize) - buf_info.bytes;
+}
+
+
+static gint oss_downsample(gpointer ob, guint length, guint speed,
+                           guint espeed);
+
+static int
+oss_calc_bitrate(int oss_fmt, int rate, int channels)
+{
+    int bitrate = rate * channels;
+
+    if (oss_fmt == AFMT_U16_BE || oss_fmt == AFMT_U16_LE ||
+        oss_fmt == AFMT_S16_BE || oss_fmt == AFMT_S16_LE)
+        bitrate *= 2;
+
+    return bitrate;
+}
+
+static int
+oss_get_format(AFormat fmt)
+{
+    int format = 0;
+
+    switch (fmt) {
+    case FMT_U8:
+        format = AFMT_U8;
+        break;
+    case FMT_S8:
+        format = AFMT_S8;
+        break;
+    case FMT_U16_LE:
+        format = AFMT_U16_LE;
+        break;
+    case FMT_U16_BE:
+        format = AFMT_U16_BE;
+        break;
+    case FMT_U16_NE:
+#if (G_BYTE_ORDER == G_BIG_ENDIAN)
+        format = AFMT_U16_BE;
+#else
+        format = AFMT_U16_LE;
+#endif
+        break;
+    case FMT_S16_LE:
+        format = AFMT_S16_LE;
+        break;
+    case FMT_S16_BE:
+        format = AFMT_S16_BE;
+        break;
+    case FMT_S16_NE:
+#if (G_BYTE_ORDER == G_BIG_ENDIAN)
+        format = AFMT_S16_BE;
+#else
+        format = AFMT_S16_LE;
+#endif
+        break;
+    }
+
+    return format;
+}
+
+static void
+oss_setup_format(AFormat fmt, int rate, int nch)
+{
+    effect.format.xmms = fmt;
+    effect.frequency = rate;
+    effect.channels = nch;
+    effect.bps = oss_calc_bitrate(oss_get_format(fmt), rate, nch);
+
+    output.format.oss = oss_get_format(fmt);
+    output.frequency = rate;
+    output.channels = nch;
+
+
+    fragsize = 0;
+    while ((1L << fragsize) < effect.bps / 25)
+        fragsize++;
+    fragsize--;
+
+    device_buffer_size = ((1L << fragsize) * (NFRAGS + 1));
+
+    oss_set_audio_params();
+
+    output.bps = oss_calc_bitrate(output.format.oss, output.frequency,
+                                  output.channels);
+}
+
+
+gint
+oss_get_written_time(void)
+{
+    if (!going)
+        return 0;
+    return (written * 1000) / effect.bps;
+}
+
+gint
+oss_get_output_time(void)
+{
+    guint64 bytes;
+
+    if (!fd || !going)
+        return 0;
+
+    if (realtime)
+        oss_calc_device_buffer_used();
+    bytes = output_bytes < device_buffer_used ?
+        0 : output_bytes - device_buffer_used;
+
+    return output_time_offset + ((bytes * 1000) / output.bps);
+}
+
+static int
+oss_used(void)
+{
+    if (realtime)
+        return 0;
+    else {
+        if (wr_index >= rd_index)
+            return wr_index - rd_index;
+        return buffer_size - (rd_index - wr_index);
+    }
+}
+
+gint
+oss_playing(void)
+{
+    if (!going)
+        return 0;
+    if (realtime)
+        oss_calc_device_buffer_used();
+    if (!oss_used() && (device_buffer_used - (3 * blk_size)) <= 0)
+        return FALSE;
+
+    return TRUE;
+}
+
+gint
+oss_free(void)
+{
+    if (!realtime) {
+        if (remove_prebuffer && prebuffer) {
+            prebuffer = FALSE;
+            remove_prebuffer = FALSE;
+        }
+        if (prebuffer)
+            remove_prebuffer = TRUE;
+
+        if (rd_index > wr_index)
+            return (rd_index - wr_index) - device_buffer_size - 1;
+        return (buffer_size - (wr_index - rd_index)) - device_buffer_size - 1;
+    }
+    else if (paused)
+        return 0;
+    else
+        return 1000000;
+}
+
+static inline ssize_t
+write_all(int fd, const void *buf, size_t count)
+{
+    size_t done = 0;
+    do {
+        ssize_t n = write(fd, (gchar *) buf + done, count - done);
+        if (n == -1) {
+            if (errno == EINTR)
+                continue;
+            else
+                break;
+        }
+        done += n;
+    } while (count > done);
+
+    return done;
+}
+
+static void
+oss_write_audio(gpointer data, int length)
+{
+
+    audio_buf_info abuf_info;
+#if 0
+    AFormat new_format;
+    int new_frequency, new_channels;
+    EffectPlugin *ep;
+
+    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(fd);
+        fd = open(device_name, O_WRONLY);
+        oss_setup_format(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);
+#endif
+    if (realtime && !ioctl(fd, SNDCTL_DSP_GETOSPACE, &abuf_info)) {
+        while (abuf_info.bytes < length) {
+            xmms_usleep(10000);
+            if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &abuf_info))
+                break;
+        }
+    }
+
+    if (oss_convert_func != NULL)
+        length = oss_convert_func(&data, length);
+
+    if (oss_stereo_convert_func != NULL)
+        length = oss_stereo_convert_func(&data, length, output.format.oss);
+
+    if (effect.frequency == output.frequency)
+        output_bytes += write_all(fd, data, length);
+    else
+        output_bytes += oss_downsample(data, length,
+                                       effect.frequency, output.frequency);
+}
+
+static void
+swap_endian(guint16 * data, int length)
+{
+    int i;
+    for (i = 0; i < length; i += 2, data++)
+        *data = GUINT16_SWAP_LE_BE(*data);
+}
+
+#define NOT_NATIVE_ENDIAN ((IS_BIG_ENDIAN &&				\
+			   (output.format.oss == AFMT_S16_LE ||		\
+			    output.format.oss == AFMT_U16_LE)) ||	\
+			  (!IS_BIG_ENDIAN &&				\
+			   (output.format.oss == AFMT_S16_BE ||		\
+			    output.format.oss == AFMT_U16_BE)))
+
+
+#define RESAMPLE_STEREO(sample_type)				\
+do {								\
+	const gint shift = sizeof (sample_type);		\
+        gint i, in_samples, out_samples, x, delta;		\
+	sample_type *inptr = (sample_type *)ob, *outptr;	\
+	guint nlen = (((length >> shift) * espeed) / speed);	\
+	if (nlen == 0)						\
+		break;						\
+	nlen <<= shift;						\
+	if (NOT_NATIVE_ENDIAN)					\
+		swap_endian(ob, length);			\
+	if(nlen > nbuffer_size)					\
+	{							\
+		nbuffer = g_realloc(nbuffer, nlen);		\
+		nbuffer_size = nlen;				\
+	}							\
+	outptr = (sample_type *)nbuffer;			\
+	in_samples = length >> shift;				\
+        out_samples = nlen >> shift;				\
+	delta = (in_samples << 12) / out_samples;		\
+	for (x = 0, i = 0; i < out_samples; i++)		\
+	{							\
+		gint x1, frac;					\
+		x1 = (x >> 12) << 12;				\
+		frac = x - x1;					\
+		*outptr++ =					\
+			(sample_type)				\
+			((inptr[(x1 >> 12) << 1] *		\
+			  ((1<<12) - frac) +			\
+			  inptr[((x1 >> 12) + 1) << 1] *	\
+			  frac) >> 12);				\
+		*outptr++ =					\
+			(sample_type)				\
+			((inptr[((x1 >> 12) << 1) + 1] *	\
+			  ((1<<12) - frac) +			\
+			  inptr[(((x1 >> 12) + 1) << 1) + 1] *	\
+			  frac) >> 12);				\
+		x += delta;					\
+	}							\
+	if (NOT_NATIVE_ENDIAN)					\
+		swap_endian(nbuffer, nlen);			\
+	w = write_all(fd, nbuffer, nlen);			\
+} while (0)
+
+
+#define RESAMPLE_MONO(sample_type)				\
+do {								\
+	const gint shift = sizeof (sample_type) - 1;		\
+        gint i, x, delta, in_samples, out_samples;		\
+	sample_type *inptr = (sample_type *)ob, *outptr;	\
+	guint nlen = (((length >> shift) * espeed) / speed);	\
+	if (nlen == 0)						\
+		break;						\
+	nlen <<= shift;						\
+	if (NOT_NATIVE_ENDIAN)					\
+		swap_endian(ob, length);			\
+	if(nlen > nbuffer_size)					\
+	{							\
+		nbuffer = g_realloc(nbuffer, nlen);		\
+		nbuffer_size = nlen;				\
+	}							\
+	outptr = (sample_type *)nbuffer;			\
+	in_samples = length >> shift;				\
+        out_samples = nlen >> shift;				\
+	delta = ((length >> shift) << 12) / out_samples;	\
+	for (x = 0, i = 0; i < out_samples; i++)		\
+	{							\
+		gint x1, frac;					\
+		x1 = (x >> 12) << 12;				\
+		frac = x - x1;					\
+		*outptr++ =					\
+			(sample_type)				\
+			((inptr[x1 >> 12] * ((1<<12) - frac) +	\
+			  inptr[(x1 >> 12) + 1] * frac) >> 12);	\
+		x += delta;					\
+	}							\
+	if (NOT_NATIVE_ENDIAN)					\
+		swap_endian(nbuffer, nlen);			\
+	w = write_all(fd, nbuffer, nlen);			\
+} while (0)
+
+
+static gint
+oss_downsample(gpointer ob, guint length, guint speed, guint espeed)
+{
+    guint w = 0;
+    static gpointer nbuffer = NULL;
+    static guint nbuffer_size = 0;
+
+    switch (output.format.oss) {
+    case AFMT_S16_BE:
+    case AFMT_S16_LE:
+        if (output.channels == 2)
+            RESAMPLE_STEREO(gint16);
+        else
+            RESAMPLE_MONO(gint16);
+        break;
+    case AFMT_U16_BE:
+    case AFMT_U16_LE:
+        if (output.channels == 2)
+            RESAMPLE_STEREO(guint16);
+        else
+            RESAMPLE_MONO(guint16);
+        break;
+    case AFMT_S8:
+        if (output.channels == 2)
+            RESAMPLE_STEREO(gint8);
+        else
+            RESAMPLE_MONO(gint8);
+        break;
+    case AFMT_U8:
+        if (output.channels == 2)
+            RESAMPLE_STEREO(guint8);
+        else
+            RESAMPLE_MONO(guint8);
+        break;
+    }
+    return w;
+}
+
+void
+oss_write(gpointer ptr, int length)
+{
+    int cnt, off = 0;
+
+    if (!realtime) {
+        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;
+        }
+    }
+    else {
+        if (paused)
+            return;
+        oss_write_audio(ptr, length);
+        written += length;
+    }
+}
+
+void
+oss_close(void)
+{
+    if (!going)
+        return;
+    going = 0;
+    if (!realtime)
+        g_thread_join(buffer_thread);
+    else {
+        ioctl(fd, SNDCTL_DSP_RESET, 0);
+        close(fd);
+    }
+    g_free(device_name);
+    oss_free_convert_buffer();
+    wr_index = 0;
+    rd_index = 0;
+
+    close_mixer_device();
+}
+
+void
+oss_flush(gint time)
+{
+    if (!realtime) {
+        flush = time;
+        while (flush != -1)
+            xmms_usleep(10000);
+    }
+    else {
+        ioctl(fd, SNDCTL_DSP_RESET, 0);
+        close(fd);
+        fd = open(device_name, O_WRONLY);
+        oss_set_audio_params();
+        output_time_offset = time;
+        written = ((guint64) time * input.bps) / 1000;
+        output_bytes = 0;
+    }
+}
+
+void
+oss_pause(short p)
+{
+    if (!realtime) {
+        if (p == TRUE)
+            do_pause = TRUE;
+        else
+            unpause = TRUE;
+    }
+    else
+        paused = p;
+
+}
+
+gpointer
+oss_loop(gpointer arg)
+{
+    gint length, cnt;
+    fd_set set;
+    struct timeval tv;
+
+    while (going) {
+        if (oss_used() > prebuffer_size)
+            prebuffer = FALSE;
+        if (oss_used() > 0 && !paused && !prebuffer) {
+            tv.tv_sec = 0;
+            tv.tv_usec = 10000;
+            FD_ZERO(&set);
+            FD_SET(fd, &set);
+            if (!select_works || (select(fd + 1, NULL, &set, NULL, &tv) > 0)) {
+                length = MIN(blk_size, oss_used());
+                while (length > 0) {
+                    cnt = MIN(length, buffer_size - rd_index);
+                    oss_write_audio(buffer + rd_index, cnt);
+                    rd_index = (rd_index + cnt) % buffer_size;
+                    length -= cnt;
+                }
+                if (!oss_used())
+                    ioctl(fd, SNDCTL_DSP_POST, 0);
+            }
+        }
+        else
+            xmms_usleep(10000);
+        oss_calc_device_buffer_used();
+        if (do_pause && !paused) {
+            do_pause = FALSE;
+            paused = TRUE;
+            /*
+             * We lose some data here that is sent to the
+             * soundcard, but not yet played.  I don't
+             * think this is worth fixing.
+             */
+            ioctl(fd, SNDCTL_DSP_RESET, 0);
+        }
+        else if (unpause && paused) {
+            unpause = FALSE;
+            close(fd);
+            fd = open(device_name, O_WRONLY);
+            oss_set_audio_params();
+            paused = FALSE;
+        }
+
+        if (flush != -1) {
+            /*
+             * This close and open is a work around of a
+             * bug that exists in some drivers which cause
+             * the driver to get fucked up by a reset
+             */
+
+            ioctl(fd, SNDCTL_DSP_RESET, 0);
+            close(fd);
+            fd = open(device_name, O_WRONLY);
+            oss_set_audio_params();
+            output_time_offset = flush;
+            written = ((guint64) flush * input.bps) / 1000;
+            rd_index = wr_index = output_bytes = 0;
+            flush = -1;
+            prebuffer = TRUE;
+        }
+
+    }
+
+    ioctl(fd, SNDCTL_DSP_RESET, 0);
+    close(fd);
+    g_free(buffer);
+    return NULL;
+}
+
+void
+oss_set_audio_params(void)
+{
+    int frag, stereo, ret;
+    struct timeval tv;
+    fd_set set;
+
+    ioctl(fd, SNDCTL_DSP_RESET, 0);
+    frag = (NFRAGS << 16) | fragsize;
+    ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag);
+    /*
+     * Set the stream format.  This ioctl() might fail, but should
+     * return a format that works if it does.
+     */
+    ioctl(fd, SNDCTL_DSP_SETFMT, &output.format.oss);
+    if (ioctl(fd, SNDCTL_DSP_SETFMT, &output.format.oss) == -1)
+        g_warning("SNDCTL_DSP_SETFMT ioctl failed: %s", strerror(errno));
+
+    stereo = output.channels - 1;
+    ioctl(fd, SNDCTL_DSP_STEREO, &stereo);
+    output.channels = stereo + 1;
+
+    oss_stereo_convert_func = oss_get_stereo_convert_func(output.channels,
+                                                          effect.channels);
+
+    if (ioctl(fd, SNDCTL_DSP_SPEED, &output.frequency) == -1)
+        g_warning("SNDCTL_DSP_SPEED ioctl failed: %s", strerror(errno));
+
+    if (ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &blk_size) == -1)
+        blk_size = 1L << fragsize;
+
+    oss_convert_func =
+        oss_get_convert_func(output.format.oss,
+                             oss_get_format(effect.format.xmms));
+
+    /*
+     * Stupid hack to find out if the driver support selects, some
+     * drivers won't work properly without a select and some won't
+     * work with a select :/
+     */
+
+    tv.tv_sec = 0;
+    tv.tv_usec = 50000;
+    FD_ZERO(&set);
+    FD_SET(fd, &set);
+    ret = select(fd + 1, NULL, &set, NULL, &tv);
+    if (ret > 0)
+        select_works = TRUE;
+    else
+        select_works = FALSE;
+}
+
+gint
+oss_open(AFormat fmt, gint rate, gint nch)
+{
+
+    if (oss_cfg.use_alt_audio_device && oss_cfg.alt_audio_device)
+        device_name = g_strdup(oss_cfg.alt_audio_device);
+    else {
+        if (oss_cfg.audio_device > 0)
+            device_name =
+                g_strdup_printf("%s%d", DEV_DSP, oss_cfg.audio_device);
+        else
+            device_name = g_strdup(DEV_DSP);
+    }
+
+    fd = open(device_name, O_WRONLY);
+
+    if (fd == -1) {
+        g_warning("oss_open(): Failed to open audio device (%s): %s",
+                  device_name, strerror(errno));
+        g_free(device_name);
+        return 0;
+    }
+
+    input.format.xmms = fmt;
+    input.frequency = rate;
+    input.channels = nch;
+    input.bps = oss_calc_bitrate(oss_get_format(fmt), rate, nch);
+
+    oss_setup_format(fmt, rate, nch);
+
+    realtime = xmms_check_realtime_priority();
+
+    if (!realtime) {
+        buffer_size = (oss_cfg.buffer_size * input.bps) / 1000;
+        if (buffer_size < 8192)
+            buffer_size = 8192;
+        prebuffer_size = (buffer_size * oss_cfg.prebuffer) / 100;
+        if (buffer_size - prebuffer_size < 4096)
+            prebuffer_size = buffer_size - 4096;
+
+        buffer_size += device_buffer_size;
+        buffer = g_malloc0(buffer_size);
+    }
+    flush = -1;
+    prebuffer = TRUE;
+    wr_index = rd_index = output_time_offset = written = output_bytes = 0;
+    paused = FALSE;
+    do_pause = FALSE;
+    unpause = FALSE;
+    remove_prebuffer = FALSE;
+
+    going = 1;
+    if (!realtime)
+        buffer_thread = g_thread_create(oss_loop, NULL, TRUE, NULL);
+    return 1;
+}
+
+void oss_tell(AFormat * fmt, gint * rate, gint * nch)
+{
+	(*fmt) = input.format.xmms;
+	(*rate) = input.frequency;
+	(*nch) = input.channels;
+}
+
+//-----------------------------OSS4 MIXER CODE-----------------------------
+static int
+open_mixer_device()  //i think we dont need this anymore
+{
+    return 0;
+}
+
+void oss_get_volume(int *l, int *r)
+{
+    int v;
+    long cmd=SNDCTL_DSP_GETPLAYVOL;
+    ioctl(fd, cmd, &v);
+    *r = (v & 0xFF00) >> 8;
+    *l = (v & 0x00FF);
+}
+
+void
+oss_set_volume(int l, int r)
+{
+    int v;
+    long cmd=SNDCTL_DSP_SETPLAYVOL;   
+    v = (r << 8) | l;
+    ioctl(fd, cmd, &v);
+}
+
+void
+close_mixer_device()
+{
+}
+
+