📄 audio.c
字号:
/****************************************************************************** * * Solaris audio plugin for XMMS * * This file contains most of the audio routines for talking to the sound * device. * *****************************************************************************/#include "Sun.h"#include "config.h"#include <pthread.h>#include <stropts.h>#include <unistd.h>#include <errno.h>#include <sys/file.h>#include <strings.h>#include <sys/modctl.h>#include <glib.h>#include <libxmms/util.h>#include <pthread.h>/*****************************************************************************//* This is the maximum data we're willing to send to the audio device and when we should refill it. */#define SUN_AUDIO_CACHE 16384/*****************************************************************************/static char *buffer_ptr = NULL;static char *buffer_end = NULL;static char *buffer_read = NULL;static char *buffer_write = NULL;static int buffer_len = 0;static pthread_mutex_t buflock = PTHREAD_MUTEX_INITIALIZER;static gint time_offset = 0;static gint byte_offset = 0;static gboolean paused = FALSE;static gboolean playing = FALSE;static gboolean aopen = FALSE;static gboolean prebuffer, remove_prebuffer;static int prebuffer_size;static volatile gint flush = -1;static gint audiofd = -2;static audio_info_t adinfo;static pthread_t buffer_thread;static uint_t audio_scount = 0;static struct { gboolean is_signed; gboolean is_bigendian; uint_t sample_rate; uint_t channels; uint_t precision; gint bps; gint output_precision;} stream_attrs;static AFormat input_format;/*****************************************************************************/void abuffer_shutdown(void);/*****************************************************************************/static char *get_audiodev(void){ if (sun_cfg.always_audiodev) { char * audiodev; audiodev = getenv("AUDIODEV"); if (audiodev != NULL) return audiodev; } return (sun_cfg.audio_device);}/* The hack in the following function relieves us of using audiotool/audiocontrol *//* in order to switch to internal CD port when playing CD Audio. */int ctlfd = -1; /* Global file descriptor for /dev/audioctl usage */ int init_ctlfd(void){ char *ctlname; int port, monitor; audio_info_t info; audio_device_t device; /* Get audio device name */ ctlname = g_strconcat(get_audiodev(), "ctl", NULL); /* Open audio control device, audio device may be in use already */ if ((ctlfd = open(ctlname, O_RDONLY, 0)) < 0) { g_free(ctlname); return -1; } g_free(ctlname); /* Get audio device characteristics */ if (ioctl(ctlfd, AUDIO_GETDEV, &device) < 0) { close(ctlfd); ctlfd = -1; return -1; } /* We can apply the trick only for sound cards using Sun driver */ if (strcmp(device.name, "SUNW,CS4231") && strcmp(device.name, "SUNW,sb16") && strcmp(device.name, "SUNW,sbpro")) return 0; /* Sun driver is not used, but it's okay */ /* Use an internal CD port if the device has one, or use line-in port */ if (ioctl(ctlfd, AUDIO_GETINFO, &info) < 0) { close(ctlfd); ctlfd = -1; return -1; } if (info.record.avail_ports & AUDIO_INTERNAL_CD_IN) port = AUDIO_INTERNAL_CD_IN; else port = AUDIO_LINE_IN; monitor = info.monitor_gain; /* Initialize info structure */ AUDIO_INITINFO(&info); /* Set port */ info.record.port = port; /* Set monitor volume so that CD sound is audible. */ /* My tests shows that 3 is a minimum value (MZ). */ if (monitor < 3) info.monitor_gain = AUDIO_MAX_GAIN / 2; /* Apply changes to audio control device */ if (ioctl(ctlfd, AUDIO_SETINFO, &info) < 0) perror(get_audiodev()); return 0;}static void audio_yield(void){ audio_info_t info; ioctl(audiofd, AUDIO_GETINFO, &info); while (playing && (flush == -1) && ((info.play.eof + 10) < audio_scount)) { xmms_usleep(10000); ioctl(audiofd, AUDIO_GETINFO, &info); }}static void audio_play(char *bufferP, int length){ int written; while (playing && (flush == -1) && (length > 0)) { written = write(audiofd, bufferP, length); if (written > 0) { length -= written; bufferP += written; if (write(audiofd, NULL, 0) == 0) { audio_scount++; audio_yield(); } } else { xmms_usleep(10000); } }}gint abuffer_get_written_time(void){ if (!playing) return 0; return ((double)byte_offset * 1000.0) / stream_attrs.bps;}gint abuffer_get_output_time(void){ if (!aopen || !playing) return 0; ioctl(audiofd, AUDIO_GETINFO, &adinfo); return time_offset + ((double)adinfo.play.samples * 1000) / adinfo.play.sample_rate;}gint abuffer_used(void){ int retval; pthread_mutex_lock(&buflock); if(buffer_write >= buffer_read) retval = buffer_write - buffer_read; else retval = buffer_len - (buffer_read - buffer_write); pthread_mutex_unlock(&buflock); return retval;}/* * Doesn't free the buffer. Just returns the number of bytes * in the buffer available to write. */gint abuffer_free(void){ int retval; /* * Two calls to this function without a write in between, * means we are at the end of the stream so stop prebuffering. */ if (remove_prebuffer && prebuffer) { prebuffer = FALSE; remove_prebuffer = FALSE; } if (prebuffer) remove_prebuffer = TRUE; /* Find the amount of buffer free (circular buffer) */ pthread_mutex_lock(&buflock); if(buffer_write >= buffer_read) retval = buffer_len - (buffer_write - buffer_read) - 1; else retval = buffer_read - buffer_write - 1; pthread_mutex_unlock(&buflock); return retval;}/***************************************************************************//* This copies data into the buffer, dependant on the endian-ness and sign *//***************************************************************************/void dsp_memcpy(void *dst, void *src, int len){ /********************************************************************** * Whether or not we have to do translations rather depends on the * architecture; use WORDS_BIGENDIAN to define this for us. We want * this bit as optimal as is humanely possible as it is called quite * often; try to catch the common situation first, and less common at * the end. *********************************************************************** * In order to keep the flow as fast as possibly, there are few 'else' * statements; instead, we use a 'return' call to get out of here. **********************************************************************/#if WORDS_BIGENDIAN if (stream_attrs.is_bigendian && stream_attrs.is_signed)#else /* WORDS_BIGENDIAN */ if (!stream_attrs.is_bigendian && stream_attrs.is_signed)#endif /* WORDS_BIGENDIAN */ { /* this is the default; simply copy the data over */ memcpy(dst, src, len); return; }/* We have either to make an endian transformation or a sign transformation or possibly both; which one(s)? */#if WORDS_BIGENDIAN if (!stream_attrs.is_bigendian)#else /* WORDS_BIGENDIAN */ if (stream_attrs.is_bigendian)#endif /* WORDS_BIGENDIAN */ { /* * Endian tranformation required * Note that since the endianness for 8 bit samples is set to native, * we can assume we have a 16 bit sample What we cannot assume is the * sign of the data */ guint16 *csrc = src, *csend = (guint16 *) ((char *) src + len); guint16 *cdst = dst; while (csrc < csend) { *cdst = GUINT16_SWAP_LE_BE(*csrc); if (!stream_attrs.is_signed) *cdst = (*cdst) ^ (1 << 15); csrc++; cdst++; } return; } /* Native endianess */ /* (!stream_attrs.is_signed) */ if (stream_attrs.precision == 16) { guint16 *csrc = src, *csend = (guint16 *) ((char *) src + len); gint16 *cdst = dst; while (csrc < csend) { *cdst++ = (*csrc) ^ (1 << 15); csrc++; } } else { guint8 *csrc = src, *csend = (guint8 *) ((char *) src + len); gint8 *cdst = dst; while (csrc < csend) { *cdst++ = (*csrc) ^ (1 << 7); csrc++; } }} gint abuffer_write_sub(void *ptr, gint length){ /* Amount to end of buffer */ int bdiff = buffer_end - buffer_write; /* Amount of space needed or max space avail if we don't have space */ int amt = MIN(length, abuffer_free()); /* Update the byte offset */ byte_offset += amt; /* Fill the buffer */ if (bdiff < amt) { dsp_memcpy(buffer_write, ptr, bdiff); dsp_memcpy(buffer_ptr, (char *) ptr + bdiff, amt - bdiff); pthread_mutex_lock(&buflock); buffer_write = buffer_ptr + amt - bdiff; pthread_mutex_unlock(&buflock); } else { dsp_memcpy(buffer_write, ptr, amt); pthread_mutex_lock(&buflock); buffer_write += amt; pthread_mutex_unlock(&buflock); } /* Return the amount actually written */ return amt;}void abuffer_write(void *ptr, gint length){ char *iptr; gint amt; void *tmp_buf; gint tmp_buf_size; gboolean need_free = FALSE; AFormat new_format; gint new_frequency, new_channels; EffectPlugin *ep; remove_prebuffer = FALSE; new_format = input_format; new_frequency = stream_attrs.sample_rate; new_channels = stream_attrs.channels; ep = get_current_effect_plugin(); if (effects_enabled() && ep && ep->query_format) { ep->query_format(&new_format,&new_frequency,&new_channels); } if (effects_enabled() && ep && ep->mod_samples) length = ep->mod_samples(&ptr,length, input_format, stream_attrs.sample_rate , stream_attrs.channels); if (new_format != input_format || new_frequency !=stream_attrs.sample_rate || new_channels != stream_attrs.channels) { /* FIXME: This is in effect a flush. Need to recalulate buffer_offset and time_offset *//* time_offset += (gint) ((output_bytes * 1000) / stream_attrs.bps); *//* output_bytes = 0; *//* oss_setup_format(new_format, new_frequency, new_channels); *//* stream_attrs.sample_rate = new_frequency; *//* stream_attrs.channels = new_channels; *//* close(fd); *//* fd = open(device_name,O_WRONLY); *//* oss_set_audio_params(); */ /* FIXME: This is leaking memory */ abuffer_open(new_format, new_frequency, new_channels); } /* Check if we're downsizing/upsizing the stream */ if (stream_attrs.precision != stream_attrs.output_precision) { gint i; /* Flag we have allocated memory for the end */ need_free = TRUE; if (stream_attrs.output_precision == 16) { /* scaling 8 => 16 */ gint8 *src = (gint8*) ptr; gint16 *target; tmp_buf_size = 2 * length; tmp_buf = g_malloc(tmp_buf_size); target = (gint16*)tmp_buf; for (i = 0; i < length; i++) { target[i] = (gint16)(src[i] ^ 128) * 256; } } else { /* scaling 16 => 8 */ gint16 *src = (gint16*) ptr; gint8 *target; /* note that length should be even, so no rounding req'd */ tmp_buf_size = length / 2; tmp_buf = g_malloc(tmp_buf_size); target = (gint8*) tmp_buf; for (i = 0; i < length; i++) { target[i] = ((gint8) (src[i] / 256)) ^ 128; } } } else { tmp_buf = ptr; tmp_buf_size = length; } iptr = tmp_buf; /* Loop till all the data is consumed */ while (tmp_buf_size > 0) { /* Write out the first section */ amt = abuffer_write_sub(iptr, tmp_buf_size); iptr += amt; tmp_buf_size -= amt; } if (need_free) g_free(tmp_buf);}void abuffer_close(void){ /* Stop the playing */ playing = FALSE; /* Rejoin the thread */ pthread_join(buffer_thread, NULL);}void abuffer_flush(gint ftime){ flush = ftime; /* Wait till */ while (flush != -1) xmms_usleep(10000);}void abuffer_pause(short p){ if (p) paused = TRUE; else paused = FALSE;}gint abuffer_startup(void){ /* Open audio device if it's not already open */ if (audiofd < 0) { if ((audiofd = open(get_audiodev(), O_WRONLY )) == -1) { perror("xmms Solaris Output plugin"); return 1; } } else { fprintf(stderr, "xmms: Warning: Attempted to reopen audio device.\n"); } /* Initialise audio device */ abuffer_set_audio_params();
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -