--- sourceforge.net/trunk/rdesktop/rdpsnd_oss.c 2003/10/19 11:59:41 504 +++ sourceforge.net/trunk/rdesktop/rdpsnd_oss.c 2006/10/26 09:47:17 1302 @@ -1,4 +1,4 @@ -/* +/* -*- c-basic-offset: 8 -*- rdesktop: A Remote Desktop Protocol client. Sound Channel Process Functions - Open Sound System Copyright (C) Matthew Chapman 2003 @@ -19,50 +19,80 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/* + This is a workaround for Esound bug 312665. + FIXME: Remove this when Esound is fixed. +*/ +#ifdef _FILE_OFFSET_BITS +#undef _FILE_OFFSET_BITS +#endif + #include "rdesktop.h" +#include "rdpsnd.h" +#include "rdpsnd_dsp.h" #include #include #include +#include +#include #include #include +#include +#include -#define MAX_QUEUE 10 +#define DEFAULTDEVICE "/dev/dsp" +#define MAX_LEN 512 -int g_dsp_fd; -BOOL g_dsp_busy = False; +static int snd_rate; +static short samplewidth; +static char *dsp_dev; +static struct audio_driver oss_driver; +static BOOL in_esddsp; -static struct audio_packet +static BOOL +detect_esddsp(void) { - struct stream s; - uint16 tick; - uint8 index; -} packet_queue[MAX_QUEUE]; -static unsigned int queue_hi, queue_lo; + struct stat s; + char *preload; + + if (fstat(g_dsp_fd, &s) == -1) + return False; + + if (S_ISCHR(s.st_mode) || S_ISBLK(s.st_mode)) + return False; + + preload = getenv("LD_PRELOAD"); + if (preload == NULL) + return False; + + if (strstr(preload, "esddsp") == NULL) + return False; + + return True; +} BOOL -wave_out_open(void) +oss_open(void) { - char *dsp_dev = "/dev/dsp"; - - if ((g_dsp_fd = open(dsp_dev, O_WRONLY | O_NONBLOCK)) == -1) + if ((g_dsp_fd = open(dsp_dev, O_WRONLY)) == -1) { perror(dsp_dev); return False; } - /* Non-blocking so that user interface is responsive */ - fcntl(g_dsp_fd, F_SETFL, fcntl(g_dsp_fd, F_GETFL) | O_NONBLOCK); + in_esddsp = detect_esddsp(); + return True; } void -wave_out_close(void) +oss_close(void) { close(g_dsp_fd); } BOOL -wave_out_format_supported(WAVEFORMATEX * pwfx) +oss_format_supported(WAVEFORMATEX * pwfx) { if (pwfx->wFormatTag != WAVE_FORMAT_PCM) return False; @@ -75,9 +105,10 @@ } BOOL -wave_out_set_format(WAVEFORMATEX * pwfx) +oss_set_format(WAVEFORMATEX * pwfx) { - int speed, channels, format; + int stereo, format, fragments; + static BOOL driver_broken = False; ioctl(g_dsp_fd, SNDCTL_DSP_RESET, NULL); ioctl(g_dsp_fd, SNDCTL_DSP_SYNC, NULL); @@ -87,6 +118,8 @@ else if (pwfx->wBitsPerSample == 16) format = AFMT_S16_LE; + samplewidth = pwfx->wBitsPerSample / 8; + if (ioctl(g_dsp_fd, SNDCTL_DSP_SETFMT, &format) == -1) { perror("SNDCTL_DSP_SETFMT"); @@ -94,99 +127,203 @@ return False; } - channels = pwfx->nChannels; - if (ioctl(g_dsp_fd, SNDCTL_DSP_CHANNELS, &channels) == -1) + if (pwfx->nChannels == 2) + { + stereo = 1; + samplewidth *= 2; + } + else + { + stereo = 0; + } + + if (ioctl(g_dsp_fd, SNDCTL_DSP_STEREO, &stereo) == -1) { perror("SNDCTL_DSP_CHANNELS"); close(g_dsp_fd); return False; } - speed = pwfx->nSamplesPerSec; - if (ioctl(g_dsp_fd, SNDCTL_DSP_SPEED, &speed) == -1) + oss_driver.need_resampling = 0; + snd_rate = pwfx->nSamplesPerSec; + if (ioctl(g_dsp_fd, SNDCTL_DSP_SPEED, &snd_rate) == -1) { - perror("SNDCTL_DSP_SPEED"); - close(g_dsp_fd); - return False; + int rates[] = { 44100, 48000, 0 }; + int *prates = rates; + + while (*prates != 0) + { + if ((pwfx->nSamplesPerSec != *prates) + && (ioctl(g_dsp_fd, SNDCTL_DSP_SPEED, prates) != -1)) + { + oss_driver.need_resampling = 1; + snd_rate = *prates; + if (rdpsnd_dsp_resample_set + (snd_rate, pwfx->wBitsPerSample, pwfx->nChannels) == False) + { + error("rdpsnd_dsp_resample_set failed"); + close(g_dsp_fd); + return False; + } + + break; + } + prates++; + } + + if (*prates == 0) + { + perror("SNDCTL_DSP_SPEED"); + close(g_dsp_fd); + return False; + } + } + + /* try to get 12 fragments of 2^12 bytes size */ + fragments = (12 << 16) + 12; + ioctl(g_dsp_fd, SNDCTL_DSP_SETFRAGMENT, &fragments); + + if (!driver_broken) + { + audio_buf_info info; + + memset(&info, 0, sizeof(info)); + if (ioctl(g_dsp_fd, SNDCTL_DSP_GETOSPACE, &info) == -1) + { + perror("SNDCTL_DSP_GETOSPACE"); + close(g_dsp_fd); + return False; + } + + if (info.fragments == 0 || info.fragstotal == 0 || info.fragsize == 0) + { + fprintf(stderr, + "Broken OSS-driver detected: fragments: %d, fragstotal: %d, fragsize: %d\n", + info.fragments, info.fragstotal, info.fragsize); + driver_broken = True; + } } return True; } void -wave_out_volume(uint16 left, uint16 right) +oss_volume(uint16 left, uint16 right) { uint32 volume; volume = left / (65536 / 100); volume |= right / (65536 / 100) << 8; + if (ioctl(g_dsp_fd, MIXER_WRITE(SOUND_MIXER_PCM), &volume) == -1) { - perror("MIXER_WRITE(SOUND_MIXER_PCM)"); + warning("hardware volume control unavailable, falling back to software volume control!\n"); + oss_driver.wave_out_volume = rdpsnd_dsp_softvol_set; + rdpsnd_dsp_softvol_set(left, right); return; } } void -wave_out_write(STREAM s, uint16 tick, uint8 index) +oss_play(void) { - struct audio_packet *packet = &packet_queue[queue_hi]; - unsigned int next_hi = (queue_hi + 1) % MAX_QUEUE; + struct audio_packet *packet; + ssize_t len; + STREAM out; - if (next_hi == queue_lo) + if (rdpsnd_queue_empty()) { - error("No space to queue audio packet\n"); + g_dsp_busy = 0; return; } - queue_hi = next_hi; + packet = rdpsnd_queue_current_packet(); + out = &packet->s; - packet->s = *s; - packet->tick = tick; - packet->index = index; - packet->s.p += 4; + len = out->end - out->p; - /* we steal the data buffer from s, give it a new one */ - s->data = malloc(s->size); + len = write(g_dsp_fd, out->p, (len > MAX_LEN) ? MAX_LEN : len); + if (len == -1) + { + if (errno != EWOULDBLOCK) + perror("write audio"); + g_dsp_busy = 1; + return; + } - if (!g_dsp_busy) - wave_out_play(); -} + out->p += len; -void -wave_out_play(void) -{ - struct audio_packet *packet; - ssize_t len; - STREAM out; - - while (1) + if (out->p == out->end) { - if (queue_lo == queue_hi) + int delay_bytes; + unsigned long delay_us; + audio_buf_info info; + + if (in_esddsp) { - g_dsp_busy = 0; - return; + /* EsounD has no way of querying buffer status, so we have to + * go with a fixed size. */ + delay_bytes = out->size; } - - packet = &packet_queue[queue_lo]; - out = &packet->s; - - len = write(g_dsp_fd, out->p, out->end - out->p); - if (len == -1) + else { - if (errno != EWOULDBLOCK) - perror("write audio"); - g_dsp_busy = 1; - return; +#ifdef SNDCTL_DSP_GETODELAY + delay_bytes = 0; + if (ioctl(g_dsp_fd, SNDCTL_DSP_GETODELAY, &delay_bytes) == -1) + delay_bytes = -1; +#else + delay_bytes = -1; +#endif + + if (delay_bytes == -1) + { + if (ioctl(g_dsp_fd, SNDCTL_DSP_GETOSPACE, &info) != -1) + delay_bytes = info.fragstotal * info.fragsize - info.bytes; + else + delay_bytes = out->size; + } } - out->p += len; - if (out->p == out->end) + delay_us = delay_bytes * (1000000 / (samplewidth * snd_rate)); + rdpsnd_queue_next(delay_us); + } + else + { + g_dsp_busy = 1; + } + + return; +} + +struct audio_driver * +oss_register(char *options) +{ + oss_driver.wave_out_open = oss_open; + oss_driver.wave_out_close = oss_close; + oss_driver.wave_out_format_supported = oss_format_supported; + oss_driver.wave_out_set_format = oss_set_format; + oss_driver.wave_out_volume = oss_volume; + oss_driver.wave_out_play = oss_play; + oss_driver.name = xstrdup("oss"); + oss_driver.description = + xstrdup("OSS output driver, default device: " DEFAULTDEVICE " or $AUDIODEV"); + oss_driver.need_byteswap_on_be = 0; + oss_driver.need_resampling = 0; + oss_driver.next = NULL; + + if (options) + { + dsp_dev = xstrdup(options); + } + else + { + dsp_dev = getenv("AUDIODEV"); + + if (dsp_dev == NULL) { - rdpsnd_send_completion(packet->tick, packet->index); - free(out->data); - queue_lo = (queue_lo + 1) % MAX_QUEUE; + dsp_dev = xstrdup(DEFAULTDEVICE); } } + return &oss_driver; }