Mercurial > audlegacy-plugins
view src/OSS/audio.c @ 3085:ac0af6b39272
Introduce new GIO plugin to buildsystem. stdio is now deprecated.
Thoughts:
- getc()/ungetc() should be moved to VFS core now
author | William Pitcock <nenolod@atheme.org> |
---|---|
date | Wed, 29 Apr 2009 20:58:36 -0500 |
parents | 87c4bc8e8ec6 |
children |
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 <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 select_works; 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 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 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; default: format = -1; } return format; } static void oss_setup_format(AFormat fmt, int rate, int nch) { output.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) < output.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) / output.bps; } gint oss_get_output_time(void) { guint64 bytes; if (!fd || !going) return 0; 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 (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 (!oss_used() && (device_buffer_used - (3 * blk_size)) <= 0) return FALSE; return TRUE; } gint oss_free(void) { 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; } static void oss_write_audio(gpointer data, size_t length) { size_t done = 0; do { ssize_t n = write(fd, (gchar *) data + done, length - done); if (n == -1) { if (errno == EINTR) continue; else break; } done += n; } while (length > done); output_bytes += done; } void oss_write(gpointer ptr, int length) { int cnt, off = 0; 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 oss_close(void) { if (!going) return; going = 0; g_thread_join(buffer_thread); g_free(device_name); wr_index = 0; rd_index = 0; close_mixer_device(); } void oss_flush(gint time) { flush = time; while (flush != -1) g_usleep(10000); } void oss_pause(short p) { if (p == TRUE) do_pause = TRUE; else unpause = TRUE; } 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 g_usleep(10000); oss_calc_device_buffer_used(); if (do_pause && !paused) { do_pause = FALSE; paused = TRUE; ioctl(fd, SNDCTL_DSP_SYNC, 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_SYNC, 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_SYNC, 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_SYNC, 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; 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; /* * 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); 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; 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; }