Mercurial > audlegacy-plugins
view src/OSS/audio.c @ 984:df7b09989aee trunk
[svn] - We got a new plugin, captain!
- FileWriter is the ultimate plugin for dumping audio to files. It
should be the successor of Disk Writer and Out-Lame, as it supports the
same output formats as those (WAVE and MP3). The main advantage of
having only one file dumping plugin for many formats is that not every
plugin has to think about file handling (where to write files to, how to
call them etc.) that much anymore.
- FileWriter is also very extensible - adding new output formats should
be very easy.
| author | mf0102 |
|---|---|
| date | Mon, 30 Apr 2007 14:16:32 -0700 |
| parents | 11d45400f5ed |
| children | 12a744b04174 |
line wrap: on
line source
/* 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 "OSS.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; 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); 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; }
